/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler.lookup; | |
import org.eclipse.wst.jsdt.core.compiler.CharOperation; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.*; | |
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.CodeStream; | |
import org.eclipse.wst.jsdt.internal.compiler.problem.ProblemReporter; | |
public class BlockScope extends Scope { | |
// Local variable management | |
public LocalVariableBinding[] locals; | |
public int localIndex; // position for next variable | |
public int startIndex; // start position in this scope - for ordering scopes vs. variables | |
public int offset; // for variable allocation throughout scopes | |
public int maxOffset; // for variable allocation throughout scopes | |
// finally scopes must be shifted behind respective try&catch scope(s) so as to avoid | |
// collisions of secret variables (return address, save value). | |
public BlockScope[] shiftScopes; | |
public final static VariableBinding[] EmulationPathToImplicitThis = {}; | |
public final static VariableBinding[] NoEnclosingInstanceInConstructorCall = {}; | |
public final static VariableBinding[] NoEnclosingInstanceInStaticContext = {}; | |
public Scope[] subscopes = new Scope[1]; // need access from code assist | |
public int subscopeCount = 0; // need access from code assist | |
// record the current case statement being processed (for entire switch case block). | |
public CaseStatement switchCase; // from 1.4 on, local types should not be accessed across switch case blocks (52221) | |
protected BlockScope(int kind, Scope parent) { | |
super(kind, parent); | |
} | |
public BlockScope(BlockScope parent) { | |
this(parent, true); | |
} | |
public BlockScope(BlockScope parent, boolean addToParentScope) { | |
this(BLOCK_SCOPE, parent); | |
locals = new LocalVariableBinding[5]; | |
if (addToParentScope) parent.addSubscope(this); | |
this.startIndex = parent.localIndex; | |
} | |
public BlockScope(BlockScope parent, int variableCount) { | |
this(BLOCK_SCOPE, parent); | |
locals = new LocalVariableBinding[variableCount]; | |
parent.addSubscope(this); | |
this.startIndex = parent.localIndex; | |
} | |
/* Create the class scope & binding for the anonymous type. | |
*/ | |
public final void addAnonymousType( | |
TypeDeclaration anonymousType, | |
ReferenceBinding superBinding) { | |
ClassScope anonymousClassScope = new ClassScope(this, anonymousType); | |
anonymousClassScope.buildAnonymousTypeBinding( | |
enclosingSourceType(), | |
superBinding); | |
} | |
/* Create the class scope & binding for the local type. | |
*/ | |
public final void addLocalType(TypeDeclaration localType) { | |
// check that the localType does not conflict with an enclosing type | |
ReferenceBinding type = enclosingSourceType(); | |
do { | |
if (CharOperation.equals(type.sourceName, localType.name)) { | |
problemReporter().hidingEnclosingType(localType); | |
return; | |
} | |
type = type.enclosingType(); | |
} while (type != null); | |
// check that the localType does not conflict with another sibling local type | |
Scope scope = this; | |
do { | |
if (((BlockScope) scope).findLocalType(localType.name) != null) { | |
problemReporter().duplicateNestedType(localType); | |
return; | |
} | |
} while ((scope = scope.parent) instanceof BlockScope); | |
ClassScope localTypeScope = new ClassScope(this, localType); | |
addSubscope(localTypeScope); | |
localTypeScope.buildLocalTypeBinding(enclosingSourceType()); | |
} | |
/* Insert a local variable into a given scope, updating its position | |
* and checking there are not too many locals or arguments allocated. | |
*/ | |
public final void addLocalVariable(LocalVariableBinding binding) { | |
checkAndSetModifiersForVariable(binding); | |
// insert local in scope | |
if (localIndex == locals.length) | |
System.arraycopy( | |
locals, | |
0, | |
(locals = new LocalVariableBinding[localIndex * 2]), | |
0, | |
localIndex); | |
locals[localIndex++] = binding; | |
// update local variable binding | |
binding.declaringScope = this; | |
binding.id = this.outerMostMethodScope().analysisIndex++; | |
// share the outermost method scope analysisIndex | |
} | |
public void addSubscope(Scope childScope) { | |
if (subscopeCount == subscopes.length) | |
System.arraycopy( | |
subscopes, | |
0, | |
(subscopes = new Scope[subscopeCount * 2]), | |
0, | |
subscopeCount); | |
subscopes[subscopeCount++] = childScope; | |
} | |
/* Answer true if the receiver is suitable for assigning final blank fields. | |
* | |
* in other words, it is inside an initializer, a constructor or a clinit | |
*/ | |
public final boolean allowBlankFinalFieldAssignment(FieldBinding binding) { | |
if (enclosingSourceType() != binding.declaringClass) | |
return false; | |
MethodScope methodScope = methodScope(); | |
if (methodScope.isStatic != binding.isStatic()) | |
return false; | |
return methodScope.isInsideInitializer() // inside initializer | |
|| ((AbstractMethodDeclaration) methodScope.referenceContext) | |
.isInitializationMethod(); // inside constructor or clinit | |
} | |
String basicToString(int tab) { | |
String newLine = "\n"; //$NON-NLS-1$ | |
for (int i = tab; --i >= 0;) | |
newLine += "\t"; //$NON-NLS-1$ | |
String s = newLine + "--- Block Scope ---"; //$NON-NLS-1$ | |
newLine += "\t"; //$NON-NLS-1$ | |
s += newLine + "locals:"; //$NON-NLS-1$ | |
for (int i = 0; i < localIndex; i++) | |
s += newLine + "\t" + locals[i].toString(); //$NON-NLS-1$ | |
s += newLine + "startIndex = " + startIndex; //$NON-NLS-1$ | |
return s; | |
} | |
private void checkAndSetModifiersForVariable(LocalVariableBinding varBinding) { | |
int modifiers = varBinding.modifiers; | |
if ((modifiers & AccAlternateModifierProblem) != 0 && varBinding.declaration != null){ | |
problemReporter().duplicateModifierForVariable(varBinding.declaration, this instanceof MethodScope); | |
} | |
int realModifiers = modifiers & AccJustFlag; | |
int unexpectedModifiers = ~AccFinal; | |
if ((realModifiers & unexpectedModifiers) != 0 && varBinding.declaration != null){ | |
problemReporter().illegalModifierForVariable(varBinding.declaration, this instanceof MethodScope); | |
} | |
varBinding.modifiers = modifiers; | |
} | |
/* Compute variable positions in scopes given an initial position offset | |
* ignoring unused local variables. | |
* | |
* No argument is expected here (ilocal is the first non-argument local of the outermost scope) | |
* Arguments are managed by the MethodScope method | |
*/ | |
void computeLocalVariablePositions(int ilocal, int initOffset, CodeStream codeStream) { | |
this.offset = initOffset; | |
this.maxOffset = initOffset; | |
// local variable init | |
int maxLocals = this.localIndex; | |
boolean hasMoreVariables = ilocal < maxLocals; | |
// scope init | |
int iscope = 0, maxScopes = this.subscopeCount; | |
boolean hasMoreScopes = maxScopes > 0; | |
// iterate scopes and variables in parallel | |
while (hasMoreVariables || hasMoreScopes) { | |
if (hasMoreScopes | |
&& (!hasMoreVariables || (subscopes[iscope].startIndex() <= ilocal))) { | |
// consider subscope first | |
if (subscopes[iscope] instanceof BlockScope) { | |
BlockScope subscope = (BlockScope) subscopes[iscope]; | |
int subOffset = subscope.shiftScopes == null ? this.offset : subscope.maxShiftedOffset(); | |
subscope.computeLocalVariablePositions(0, subOffset, codeStream); | |
if (subscope.maxOffset > this.maxOffset) | |
this.maxOffset = subscope.maxOffset; | |
} | |
hasMoreScopes = ++iscope < maxScopes; | |
} else { | |
// consider variable first | |
LocalVariableBinding local = locals[ilocal]; // if no local at all, will be locals[ilocal]==null | |
// check if variable is actually used, and may force it to be preserved | |
boolean generateCurrentLocalVar = (local.useFlag == LocalVariableBinding.USED && !local.isConstantValue()); | |
// do not report fake used variable | |
if (local.useFlag == LocalVariableBinding.UNUSED | |
&& (local.declaration != null) // unused (and non secret) local | |
&& ((local.declaration.bits & ASTNode.IsLocalDeclarationReachableMASK) != 0)) { // declaration is reachable | |
if (!(local.declaration instanceof Argument)) // do not report unused catch arguments | |
this.problemReporter().unusedLocalVariable(local.declaration); | |
} | |
// could be optimized out, but does need to preserve unread variables ? | |
if (!generateCurrentLocalVar) { | |
if (local.declaration != null && environment().options.preserveAllLocalVariables) { | |
generateCurrentLocalVar = true; // force it to be preserved in the generated code | |
local.useFlag = LocalVariableBinding.USED; | |
} | |
} | |
// allocate variable | |
if (generateCurrentLocalVar) { | |
if (local.declaration != null) { | |
codeStream.record(local); // record user-defined local variables for attribute generation | |
} | |
// assign variable position | |
local.resolvedPosition = this.offset; | |
if ((local.type == LongBinding) || (local.type == DoubleBinding)) { | |
this.offset += 2; | |
} else { | |
this.offset++; | |
} | |
if (this.offset > 0xFFFF) { // no more than 65535 words of locals | |
this.problemReporter().noMoreAvailableSpaceForLocal( | |
local, | |
local.declaration == null ? (ASTNode)this.methodScope().referenceContext : local.declaration); | |
} | |
} else { | |
local.resolvedPosition = -1; // not generated | |
} | |
hasMoreVariables = ++ilocal < maxLocals; | |
} | |
} | |
if (this.offset > this.maxOffset) | |
this.maxOffset = this.offset; | |
} | |
/* | |
* Record the suitable binding denoting a synthetic field or constructor argument, | |
* mapping to the actual outer local variable in the scope context. | |
* Note that this may not need any effect, in case the outer local variable does not | |
* need to be emulated and can directly be used as is (using its back pointer to its | |
* declaring scope). | |
*/ | |
public void emulateOuterAccess(LocalVariableBinding outerLocalVariable) { | |
MethodScope currentMethodScope; | |
if ((currentMethodScope = this.methodScope()) | |
!= outerLocalVariable.declaringScope.methodScope()) { | |
NestedTypeBinding currentType = (NestedTypeBinding) this.enclosingSourceType(); | |
//do nothing for member types, pre emulation was performed already | |
if (!currentType.isLocalType()) { | |
return; | |
} | |
// must also add a synthetic field if we're not inside a constructor | |
if (!currentMethodScope.isInsideInitializerOrConstructor()) { | |
currentType.addSyntheticArgumentAndField(outerLocalVariable); | |
} else { | |
currentType.addSyntheticArgument(outerLocalVariable); | |
} | |
} | |
} | |
/* Note that it must never produce a direct access to the targetEnclosingType, | |
* but instead a field sequence (this$2.this$1.this$0) so as to handle such a test case: | |
* | |
* class XX { | |
* void foo() { | |
* class A { | |
* class B { | |
* class C { | |
* boolean foo() { | |
* return (Object) A.this == (Object) B.this; | |
* } | |
* } | |
* } | |
* } | |
* new A().new B().new C(); | |
* } | |
* } | |
* where we only want to deal with ONE enclosing instance for C (could not figure out an A for C) | |
*/ | |
public final ReferenceBinding findLocalType(char[] name) { | |
long compliance = environment().options.complianceLevel; | |
for (int i = 0, length = subscopeCount; i < length; i++) { | |
if (subscopes[i] instanceof ClassScope) { | |
LocalTypeBinding sourceType = (LocalTypeBinding)((ClassScope) subscopes[i]).referenceContext.binding; | |
// from 1.4 on, local types should not be accessed across switch case blocks (52221) | |
if (compliance >= ClassFileConstants.JDK1_4 && sourceType.switchCase != this.switchCase) continue; | |
if (CharOperation.equals(sourceType.sourceName(), name)) | |
return sourceType; | |
} | |
} | |
return null; | |
} | |
public LocalVariableBinding findVariable(char[] variable) { | |
int varLength = variable.length; | |
for (int i = 0, length = locals.length; i < length; i++) { | |
LocalVariableBinding local = locals[i]; | |
if (local == null) | |
return null; | |
if (local.name.length == varLength && CharOperation.equals(local.name, variable)) | |
return local; | |
} | |
return null; | |
} | |
/* API | |
* flag is a mask of the following values VARIABLE (= FIELD or LOCAL), TYPE. | |
* Only bindings corresponding to the mask will be answered. | |
* | |
* if the VARIABLE mask is set then | |
* If the first name provided is a field (or local) then the field (or local) is answered | |
* Otherwise, package names and type names are consumed until a field is found. | |
* In this case, the field is answered. | |
* | |
* if the TYPE mask is set, | |
* package names and type names are consumed until the end of the input. | |
* Only if all of the input is consumed is the type answered | |
* | |
* All other conditions are errors, and a problem binding is returned. | |
* | |
* NOTE: If a problem binding is returned, senders should extract the compound name | |
* from the binding & not assume the problem applies to the entire compoundName. | |
* | |
* The VARIABLE mask has precedence over the TYPE mask. | |
* | |
* InvocationSite implements | |
* isSuperAccess(); this is used to determine if the discovered field is visible. | |
* setFieldIndex(int); this is used to record the number of names that were consumed. | |
* | |
* For example, getBinding({"foo","y","q", VARIABLE, site) will answer | |
* the binding for the field or local named "foo" (or an error binding if none exists). | |
* In addition, setFieldIndex(1) will be sent to the invocation site. | |
* If a type named "foo" exists, it will not be detected (and an error binding will be answered) | |
* | |
* IMPORTANT NOTE: This method is written under the assumption that compoundName is longer than length 1. | |
*/ | |
public Binding getBinding(char[][] compoundName, int mask, InvocationSite invocationSite, boolean needResolve) { | |
Binding binding = getBinding(compoundName[0], mask | TYPE | PACKAGE, invocationSite, needResolve); | |
invocationSite.setFieldIndex(1); | |
if (binding instanceof VariableBinding) return binding; | |
compilationUnitScope().recordSimpleReference(compoundName[0]); | |
if (!binding.isValidBinding()) return binding; | |
int length = compoundName.length; | |
int currentIndex = 1; | |
foundType : if (binding instanceof PackageBinding) { | |
PackageBinding packageBinding = (PackageBinding) binding; | |
while (currentIndex < length) { | |
compilationUnitScope().recordReference(packageBinding.compoundName, compoundName[currentIndex]); | |
binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++]); | |
invocationSite.setFieldIndex(currentIndex); | |
if (binding == null) { | |
if (currentIndex == length) { | |
// must be a type if its the last name, otherwise we have no idea if its a package or type | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
return new ProblemBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
if (binding instanceof ReferenceBinding) { | |
if (!binding.isValidBinding()) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
if (!((ReferenceBinding) binding).canBeSeenBy(this)) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
(ReferenceBinding) binding, | |
NotVisible); | |
break foundType; | |
} | |
packageBinding = (PackageBinding) binding; | |
} | |
// It is illegal to request a PACKAGE from this method. | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
// know binding is now a ReferenceBinding | |
while (currentIndex < length) { | |
ReferenceBinding typeBinding = (ReferenceBinding) binding; | |
char[] nextName = compoundName[currentIndex++]; | |
invocationSite.setFieldIndex(currentIndex); | |
invocationSite.setActualReceiverType(typeBinding); | |
if ((mask & FIELD) != 0 && (binding = findField(typeBinding, nextName, invocationSite, true /*resolve*/)) != null) { | |
if (!binding.isValidBinding()) | |
return new ProblemFieldBinding( | |
((FieldBinding) binding).declaringClass, | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
break; // binding is now a field | |
} | |
if ((binding = findMemberType(nextName, typeBinding)) == null) { | |
if ((mask & FIELD) != 0) { | |
return new ProblemBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
typeBinding, | |
NotFound); | |
} | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
typeBinding, | |
NotFound); | |
} | |
if (!binding.isValidBinding()) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
} | |
if ((mask & FIELD) != 0 && (binding instanceof FieldBinding)) { | |
// was looking for a field and found a field | |
FieldBinding field = (FieldBinding) binding; | |
if (!field.isStatic()) | |
return new ProblemFieldBinding( | |
field.declaringClass, | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NonStaticReferenceInStaticContext); | |
return binding; | |
} | |
if ((mask & TYPE) != 0 && (binding instanceof ReferenceBinding)) { | |
// was looking for a type and found a type | |
return binding; | |
} | |
// handle the case when a field or type was asked for but we resolved the compoundName to a type or field | |
return new ProblemBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
// Added for code assist... NOT Public API | |
public final Binding getBinding( | |
char[][] compoundName, | |
InvocationSite invocationSite) { | |
int currentIndex = 0; | |
int length = compoundName.length; | |
Binding binding = | |
getBinding( | |
compoundName[currentIndex++], | |
VARIABLE | TYPE | PACKAGE, | |
invocationSite, | |
true /*resolve*/); | |
if (!binding.isValidBinding()) | |
return binding; | |
foundType : if (binding instanceof PackageBinding) { | |
while (currentIndex < length) { | |
PackageBinding packageBinding = (PackageBinding) binding; | |
binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++]); | |
if (binding == null) { | |
if (currentIndex == length) { | |
// must be a type if its the last name, otherwise we have no idea if its a package or type | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
return new ProblemBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
} | |
if (binding instanceof ReferenceBinding) { | |
if (!binding.isValidBinding()) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
if (!((ReferenceBinding) binding).canBeSeenBy(this)) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
(ReferenceBinding) binding, | |
NotVisible); | |
break foundType; | |
} | |
} | |
return binding; | |
} | |
foundField : if (binding instanceof ReferenceBinding) { | |
while (currentIndex < length) { | |
ReferenceBinding typeBinding = (ReferenceBinding) binding; | |
char[] nextName = compoundName[currentIndex++]; | |
if ((binding = findField(typeBinding, nextName, invocationSite, true /*resolve*/)) != null) { | |
if (!binding.isValidBinding()) | |
return new ProblemFieldBinding( | |
((FieldBinding) binding).declaringClass, | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
if (!((FieldBinding) binding).isStatic()) | |
return new ProblemFieldBinding( | |
((FieldBinding) binding).declaringClass, | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NonStaticReferenceInStaticContext); | |
break foundField; // binding is now a field | |
} | |
if ((binding = findMemberType(nextName, typeBinding)) == null) | |
return new ProblemBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
typeBinding, | |
NotFound); | |
if (!binding.isValidBinding()) | |
return new ProblemReferenceBinding( | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
binding.problemId()); | |
} | |
return binding; | |
} | |
VariableBinding variableBinding = (VariableBinding) binding; | |
while (currentIndex < length) { | |
TypeBinding typeBinding = variableBinding.type; | |
if (typeBinding == null) | |
return new ProblemFieldBinding( | |
null, | |
CharOperation.subarray(compoundName, 0, currentIndex + 1), | |
NotFound); | |
variableBinding = | |
findField(typeBinding, compoundName[currentIndex++], invocationSite, true /*resolve*/); | |
if (variableBinding == null) | |
return new ProblemFieldBinding( | |
null, | |
CharOperation.subarray(compoundName, 0, currentIndex), | |
NotFound); | |
if (!variableBinding.isValidBinding()) | |
return variableBinding; | |
} | |
return variableBinding; | |
} | |
/* | |
* This retrieves the argument that maps to an enclosing instance of the suitable type, | |
* if not found then answers nil -- do not create one | |
* | |
* #implicitThis : the implicit this will be ok | |
* #((arg) this$n) : available as a constructor arg | |
* #((arg) this$n ... this$p) : available as as a constructor arg + a sequence of fields | |
* #((fieldDescr) this$n ... this$p) : available as a sequence of fields | |
* nil : not found | |
* | |
* Note that this algorithm should answer the shortest possible sequence when | |
* shortcuts are available: | |
* this$0 . this$0 . this$0 | |
* instead of | |
* this$2 . this$1 . this$0 . this$1 . this$0 | |
* thus the code generation will be more compact and runtime faster | |
*/ | |
public VariableBinding[] getEmulationPath(LocalVariableBinding outerLocalVariable) { | |
MethodScope currentMethodScope = this.methodScope(); | |
SourceTypeBinding sourceType = currentMethodScope.enclosingSourceType(); | |
// identity check | |
if (currentMethodScope == outerLocalVariable.declaringScope.methodScope()) { | |
return new VariableBinding[] { outerLocalVariable }; | |
// implicit this is good enough | |
} | |
// use synthetic constructor arguments if possible | |
if (currentMethodScope.isInsideInitializerOrConstructor() | |
&& (sourceType.isNestedType())) { | |
SyntheticArgumentBinding syntheticArg; | |
if ((syntheticArg = ((NestedTypeBinding) sourceType).getSyntheticArgument(outerLocalVariable)) != null) { | |
return new VariableBinding[] { syntheticArg }; | |
} | |
} | |
// use a synthetic field then | |
if (!currentMethodScope.isStatic) { | |
FieldBinding syntheticField; | |
if ((syntheticField = sourceType.getSyntheticField(outerLocalVariable)) != null) { | |
return new VariableBinding[] { syntheticField }; | |
} | |
} | |
return null; | |
} | |
/* | |
* This retrieves the argument that maps to an enclosing instance of the suitable type, | |
* if not found then answers nil -- do not create one | |
* | |
* #implicitThis : the implicit this will be ok | |
* #((arg) this$n) : available as a constructor arg | |
* #((arg) this$n access$m... access$p) : available as as a constructor arg + a sequence of synthetic accessors to synthetic fields | |
* #((fieldDescr) this$n access#m... access$p) : available as a first synthetic field + a sequence of synthetic accessors to synthetic fields | |
* null : not found | |
* jls 15.9.2 + http://www.ergnosis.com/java-spec-report/java-language/jls-8.8.5.1-d.html | |
*/ | |
public Object[] getEmulationPath( | |
ReferenceBinding targetEnclosingType, | |
boolean onlyExactMatch, | |
boolean ignoreEnclosingArgInConstructorCall) { | |
MethodScope currentMethodScope = this.methodScope(); | |
SourceTypeBinding sourceType = currentMethodScope.enclosingSourceType(); | |
// use 'this' if possible | |
if (!currentMethodScope.isConstructorCall && !currentMethodScope.isStatic) { | |
if (sourceType == targetEnclosingType || (!onlyExactMatch && sourceType.findSuperTypeErasingTo(targetEnclosingType) != null)) { | |
return EmulationPathToImplicitThis; // implicit this is good enough | |
} | |
} | |
if (!sourceType.isNestedType() || sourceType.isStatic()) { // no emulation from within non-inner types | |
if (currentMethodScope.isConstructorCall) { | |
return NoEnclosingInstanceInConstructorCall; | |
} else if (currentMethodScope.isStatic){ | |
return NoEnclosingInstanceInStaticContext; | |
} | |
return null; | |
} | |
boolean insideConstructor = currentMethodScope.isInsideInitializerOrConstructor(); | |
// use synthetic constructor arguments if possible | |
if (insideConstructor) { | |
SyntheticArgumentBinding syntheticArg; | |
if ((syntheticArg = ((NestedTypeBinding) sourceType).getSyntheticArgument(targetEnclosingType, onlyExactMatch)) != null) { | |
// reject allocation and super constructor call | |
if (ignoreEnclosingArgInConstructorCall | |
&& currentMethodScope.isConstructorCall | |
&& (sourceType == targetEnclosingType || (!onlyExactMatch && sourceType.findSuperTypeErasingTo(targetEnclosingType) != null))) { | |
return NoEnclosingInstanceInConstructorCall; | |
} | |
return new Object[] { syntheticArg }; | |
} | |
} | |
// use a direct synthetic field then | |
if (currentMethodScope.isStatic) { | |
return NoEnclosingInstanceInStaticContext; | |
} | |
FieldBinding syntheticField = sourceType.getSyntheticField(targetEnclosingType, onlyExactMatch); | |
if (syntheticField != null) { | |
if (currentMethodScope.isConstructorCall){ | |
return NoEnclosingInstanceInConstructorCall; | |
} | |
return new Object[] { syntheticField }; | |
} | |
// could be reached through a sequence of enclosing instance link (nested members) | |
Object[] path = new Object[2]; // probably at least 2 of them | |
ReferenceBinding currentType = sourceType.enclosingType(); | |
if (insideConstructor) { | |
path[0] = ((NestedTypeBinding) sourceType).getSyntheticArgument(currentType, onlyExactMatch); | |
} else { | |
if (currentMethodScope.isConstructorCall){ | |
return NoEnclosingInstanceInConstructorCall; | |
} | |
path[0] = sourceType.getSyntheticField(currentType, onlyExactMatch); | |
} | |
if (path[0] != null) { // keep accumulating | |
int count = 1; | |
ReferenceBinding currentEnclosingType; | |
while ((currentEnclosingType = currentType.enclosingType()) != null) { | |
//done? | |
if (currentType == targetEnclosingType | |
|| (!onlyExactMatch && currentType.findSuperTypeErasingTo(targetEnclosingType) != null)) break; | |
if (currentMethodScope != null) { | |
currentMethodScope = currentMethodScope.enclosingMethodScope(); | |
if (currentMethodScope != null && currentMethodScope.isConstructorCall){ | |
return NoEnclosingInstanceInConstructorCall; | |
} | |
if (currentMethodScope != null && currentMethodScope.isStatic){ | |
return NoEnclosingInstanceInStaticContext; | |
} | |
} | |
syntheticField = ((NestedTypeBinding) currentType).getSyntheticField(currentEnclosingType, onlyExactMatch); | |
if (syntheticField == null) break; | |
// append inside the path | |
if (count == path.length) { | |
System.arraycopy(path, 0, (path = new Object[count + 1]), 0, count); | |
} | |
// private access emulation is necessary since synthetic field is private | |
path[count++] = ((SourceTypeBinding) syntheticField.declaringClass).addSyntheticMethod(syntheticField, true); | |
currentType = currentEnclosingType; | |
} | |
if (currentType == targetEnclosingType | |
|| (!onlyExactMatch && currentType.findSuperTypeErasingTo(targetEnclosingType) != null)) { | |
return path; | |
} | |
} | |
return null; | |
} | |
/* Answer true if the variable name already exists within the receiver's scope. | |
*/ | |
public final boolean isDuplicateLocalVariable(char[] name) { | |
BlockScope current = this; | |
while (true) { | |
for (int i = 0; i < localIndex; i++) { | |
if (CharOperation.equals(name, current.locals[i].name)) | |
return true; | |
} | |
if (current.kind != BLOCK_SCOPE) return false; | |
current = (BlockScope)current.parent; | |
} | |
} | |
public int maxShiftedOffset() { | |
int max = -1; | |
if (this.shiftScopes != null){ | |
for (int i = 0, length = this.shiftScopes.length; i < length; i++){ | |
int subMaxOffset = this.shiftScopes[i].maxOffset; | |
if (subMaxOffset > max) max = subMaxOffset; | |
} | |
} | |
return max; | |
} | |
/* Answer the problem reporter to use for raising new problems. | |
* | |
* Note that as a side-effect, this updates the current reference context | |
* (unit, type or method) in case the problem handler decides it is necessary | |
* to abort. | |
*/ | |
public ProblemReporter problemReporter() { | |
return outerMostMethodScope().problemReporter(); | |
} | |
/* | |
* Code responsible to request some more emulation work inside the invocation type, so as to supply | |
* correct synthetic arguments to any allocation of the target type. | |
*/ | |
public void propagateInnerEmulation(ReferenceBinding targetType, boolean isEnclosingInstanceSupplied) { | |
// no need to propagate enclosing instances, they got eagerly allocated already. | |
SyntheticArgumentBinding[] syntheticArguments; | |
if ((syntheticArguments = targetType.syntheticOuterLocalVariables()) != null) { | |
for (int i = 0, max = syntheticArguments.length; i < max; i++) { | |
SyntheticArgumentBinding syntheticArg = syntheticArguments[i]; | |
// need to filter out the one that could match a supplied enclosing instance | |
if (!(isEnclosingInstanceSupplied | |
&& (syntheticArg.type == targetType.enclosingType()))) { | |
this.emulateOuterAccess(syntheticArg.actualOuterLocalVariable); | |
} | |
} | |
} | |
} | |
/* Answer the reference type of this scope. | |
* | |
* It is the nearest enclosing type of this scope. | |
*/ | |
public TypeDeclaration referenceType() { | |
return methodScope().referenceType(); | |
} | |
/* | |
* Answer the index of this scope relatively to its parent. | |
* For method scope, answers -1 (not a classScope relative position) | |
*/ | |
public int scopeIndex() { | |
if (this instanceof MethodScope) return -1; | |
BlockScope parentScope = (BlockScope)parent; | |
Scope[] parentSubscopes = parentScope.subscopes; | |
for (int i = 0, max = parentScope.subscopeCount; i < max; i++) { | |
if (parentSubscopes[i] == this) return i; | |
} | |
return -1; | |
} | |
// start position in this scope - for ordering scopes vs. variables | |
int startIndex() { | |
return startIndex; | |
} | |
public String toString() { | |
return toString(0); | |
} | |
public String toString(int tab) { | |
String s = basicToString(tab); | |
for (int i = 0; i < subscopeCount; i++) | |
if (subscopes[i] instanceof BlockScope) | |
s += ((BlockScope) subscopes[i]).toString(tab + 1) + "\n"; //$NON-NLS-1$ | |
return s; | |
} | |
} |