Skip to content

Commit e49ba49

Browse files
[LVTI] Review and restructuring of the implementation for JEP 286 - Local Variable Type Inference (eclipse-jdt#4164)
- Unspaghettify `LocalDeclaration.resolve` - Alternate way of reporting `IProblem.VarLocalReferencesItself` that reuses existing infrastructure without inventing additional wheels. - Streamline and simplify error handling - Unify var type declaration handling across different contexts: locals, foreach element variables, component patterns ... * Fixes eclipse-jdt#4165 * Fixes eclipse-jdt#4163
1 parent 69db774 commit e49ba49

File tree

17 files changed

+218
-223
lines changed

17 files changed

+218
-223
lines changed

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,9 +2356,13 @@ public interface IProblem {
23562356
int VarLocalInitializedToVoid = TypeRelated + 1505; // Variable initializer is ''void'' -- cannot infer variable type
23572357
/** @since 3.14 */
23582358
int VarLocalCannotBeArrayInitalizers = TypeRelated + 1506; // Array initializer needs an explicit target-type
2359-
/** @since 3.14 */
2359+
/** @since 3.14
2360+
* @deprecated no longer issued - will be removed
2361+
* */
23602362
int VarLocalCannotBeLambda = TypeRelated + 1507; // Lambda expression needs an explicit target-type
2361-
/** @since 3.14 */
2363+
/** @since 3.14
2364+
* @deprecated no longer issued - will be removed
2365+
* */
23622366
int VarLocalCannotBeMethodReference = TypeRelated + 1508; // Method reference needs an explicit target-type
23632367
/** @since 3.14 */
23642368
int VarIsReserved = Syntax + 1509; // ''var'' is not a valid type name

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public abstract class ASTNode implements TypeConstants, TypeIds {
159159
public static final int IsForeachElementVariable = Bit5;
160160
public static final int ShadowsOuterLocal = Bit22;
161161
public static final int IsAdditionalDeclarator = Bit23;
162+
public static final int IsPatternVariable = Bit24;
162163

163164
// for name refs or local decls
164165
public static final int FirstAssignmentToLocal = Bit4;

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -475,21 +475,11 @@ public void resolve(BlockScope upperScope) {
475475

476476
// Patch the resolved type
477477
if (this.elementVariable.isTypeNameVar(upperScope)) {
478-
if (this.elementVariable.type.dimensions() > 0 || this.elementVariable.type.extraDimensions() > 0) {
479-
upperScope.problemReporter().varLocalCannotBeArray(this.elementVariable);
480-
}
481-
if (TypeBinding.equalsEquals(TypeBinding.NULL, collectionType)) {
482-
upperScope.problemReporter().varLocalInitializedToNull(this.elementVariable);
483-
elementType = collectionType;
484-
} else if (TypeBinding.equalsEquals(TypeBinding.VOID, collectionType)) {
485-
upperScope.problemReporter().varLocalInitializedToVoid(this.elementVariable);
486-
elementType = collectionType;
487-
}
488-
if ((elementType = getCollectionElementType(this.scope, collectionType)) == null) {
489-
elementType = collectionType;
490-
} else {
491-
elementType = this.elementVariable.patchType(elementType);
492-
}
478+
if ((elementType = getCollectionElementType(this.scope, collectionType)) == null)
479+
elementType = this.scope.getJavaLangObject(); // error reported below.
480+
else
481+
elementType = this.elementVariable.patchType(this.scope, elementType);
482+
493483
if (elementType instanceof ReferenceBinding) {
494484
ReferenceBinding refBinding = (ReferenceBinding) elementType;
495485
if (!elementType.canBeSeenBy(upperScope)) {

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java

Lines changed: 56 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,11 @@ public void getAllAnnotationContexts(int targetType, int parameterIndex, List<An
213213
public boolean isReceiver() {
214214
return false;
215215
}
216-
public TypeBinding patchType(TypeBinding newType) {
216+
public TypeBinding patchType(BlockScope scope, TypeBinding newType) {
217+
if (TypeBinding.equalsEquals(TypeBinding.NULL, newType))
218+
scope.problemReporter().varLocalInitializedToNull(this);
219+
else if (TypeBinding.equalsEquals(TypeBinding.VOID, newType))
220+
scope.problemReporter().varLocalInitializedToVoid(this);
217221
// Perform upwards projection on type wrt mentioned type variables
218222
TypeBinding[] mentionedTypeVariables= newType != null ? newType.syntheticTypeVariablesMentioned() : null;
219223
if (mentionedTypeVariables != null && mentionedTypeVariables.length > 0) {
@@ -222,63 +226,31 @@ public TypeBinding patchType(TypeBinding newType) {
222226
this.type.resolvedType = newType;
223227
if (this.binding != null) {
224228
this.binding.type = newType;
225-
this.binding.markInitialized();
226229
}
227230
return this.type.resolvedType;
228231
}
229232

230-
private static Expression findPolyExpression(Expression e) {
231-
// This is simpler than using an ASTVisitor, since we only care about a very few select cases.
232-
if (e instanceof FunctionalExpression) {
233-
return e;
234-
}
235-
if (e instanceof ConditionalExpression) {
236-
ConditionalExpression ce = (ConditionalExpression)e;
237-
Expression candidate = findPolyExpression(ce.valueIfTrue);
238-
if (candidate == null) {
239-
candidate = findPolyExpression(ce.valueIfFalse);
240-
}
241-
if (candidate != null) return candidate;
242-
}
243-
if (e instanceof SwitchExpression se) {
244-
for (Expression re : se.resultExpressions()) {
245-
Expression candidate = findPolyExpression(re);
246-
if (candidate != null) return candidate;
247-
}
248-
}
249-
return null;
250-
}
251-
252233
@Override
253234
public void resolve(BlockScope scope) {
254-
resolve(scope, false);
255-
}
256-
public void resolve(BlockScope scope, boolean isPatternVariable) { // prescan NNBD
257-
handleNonNullByDefault(scope, this.annotations, this);
258235

259-
if (!isPatternVariable && (this.bits & ASTNode.IsForeachElementVariable) == 0 && this.initialization == null && this.isUnnamed(scope)) {
260-
scope.problemReporter().unnamedVariableMustHaveInitializer(this);
261-
}
236+
handleNonNullByDefault(scope, this.annotations, this); // prescan NNBD
237+
238+
final boolean isPatternVariable = (this.bits & ASTNode.IsPatternVariable) != 0;
239+
final boolean isForeachElementVariable = (this.bits & ASTNode.IsForeachElementVariable) != 0;
240+
final boolean varTypedLocal = isTypeNameVar(scope);
241+
final boolean unnamedLocal = isUnnamed(scope);
262242

263243
TypeBinding variableType = null;
264-
boolean variableTypeInferenceError = false;
265-
boolean isTypeNameVar = isTypeNameVar(scope);
266-
if (isTypeNameVar && !isPatternVariable) {
267-
if (this.type.isParameterizedTypeReference()) {
244+
245+
if (varTypedLocal) {
246+
if (this.type.isParameterizedTypeReference())
268247
scope.problemReporter().varCannotBeUsedWithTypeArguments(this.type);
269-
}
270-
if ((this.bits & ASTNode.IsForeachElementVariable) == 0) {
271-
// infer a type from the initializer
272-
if (this.initialization != null) {
273-
variableType = checkInferredLocalVariableInitializer(scope);
274-
variableTypeInferenceError = variableType != null;
275-
} else {
276-
// That's always an error
277-
scope.problemReporter().varLocalWithoutInitizalier(this);
278-
variableType = scope.getJavaLangObject();
279-
variableTypeInferenceError = true;
280-
}
281-
}
248+
if (this.type.dimensions() > 0 || this.type.extraDimensions() > 0)
249+
scope.problemReporter().varLocalCannotBeArray(this);
250+
if ((this.bits & ASTNode.IsAdditionalDeclarator) != 0)
251+
scope.problemReporter().varLocalMultipleDeclarators(this);
252+
if (isPatternVariable)
253+
variableType = this.type.resolvedType; // set already if this is a component pattern or is problem binding as it should be.
282254
} else {
283255
variableType = this.type == null ? null : this.type.resolveType(scope, true /* check bounds*/);
284256
}
@@ -297,14 +269,14 @@ public void resolve(BlockScope scope, boolean isPatternVariable) { // prescan N
297269
}
298270
}
299271

300-
Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
301-
if (existingVariable != null && existingVariable.isValidBinding() && !this.isUnnamed(scope)) {
272+
Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
273+
if (existingVariable != null && existingVariable.isValidBinding() && !unnamedLocal) {
302274
boolean localExists = existingVariable instanceof LocalVariableBinding;
303-
if (localExists && (this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope() && this.hiddenVariableDepth == 0) {
275+
if (localExists && (this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope() && this.hiddenVariableDepth == 0) {
304276
scope.problemReporter().lambdaRedeclaresLocal(this);
305277
} else if (localExists && this.hiddenVariableDepth == 0) {
306278
if (existingVariable.isPatternVariable()) {
307-
scope.problemReporter().illegalRedeclarationOfPatternVar((LocalVariableBinding) existingVariable, this);
279+
scope.problemReporter().illegalRedeclarationOfPatternVar((LocalVariableBinding) existingVariable, this);
308280
} else {
309281
scope.problemReporter().redefineLocal(this);
310282
}
@@ -313,66 +285,22 @@ public void resolve(BlockScope scope, boolean isPatternVariable) { // prescan N
313285
}
314286
}
315287
}
316-
if ((this.modifiers & ClassFileConstants.AccFinal)!= 0 && this.initialization == null) {
288+
if ((this.modifiers & ClassFileConstants.AccFinal) != 0 && this.initialization == null) {
317289
this.modifiers |= ExtraCompilerModifiers.AccBlankFinal;
318290
}
319-
if (isTypeNameVar) {
320-
// Create binding for the initializer's type
321-
// In order to resolve self-referential initializers, we must declare the variable with a placeholder type (j.l.Object), and then patch it later
322-
this.binding = new LocalVariableBinding(this, variableType != null ? variableType : scope.getJavaLangObject(), this.modifiers, false) {
323-
private boolean isInitialized = false;
324-
325-
@Override
326-
public void markReferenced() {
327-
if (! this.isInitialized) {
328-
scope.problemReporter().varLocalReferencesItself(LocalDeclaration.this);
329-
this.type = null;
330-
this.isInitialized = true; // Quell additional type errors
331-
}
332-
}
333-
@Override
334-
public void markInitialized() {
335-
this.isInitialized = true;
336-
}
337-
};
338-
} else {
339-
// create a binding from the specified type
340-
this.binding = new LocalVariableBinding(this, variableType, this.modifiers, false /*isArgument*/);
341-
}
291+
// Create a binding from the specified type; In order to diagnose self-referential initializers,
292+
// we must create the binding with jlO as a placeholder type and patch it later
293+
this.binding = new LocalVariableBinding(this, varTypedLocal && variableType == null ? scope.getJavaLangObject() : variableType, this.modifiers, false /*isArgument*/);
342294
if (isPatternVariable)
343295
this.binding.tagBits |= TagBits.IsPatternBinding;
344296
scope.addLocalVariable(this.binding);
345297
this.binding.setConstant(Constant.NotAConstant);
346-
// allow to recursivelly target the binding....
298+
// allow to recursively target the binding....
347299
// the correct constant is harmed if correctly computed at the end of this method
348300

349-
if (variableType == null) {
350-
if (this.initialization != null) {
351-
if (this.initialization instanceof CastExpression) {
352-
((CastExpression)this.initialization).setVarTypeDeclaration(true);
353-
}
354-
this.initialization.resolveType(scope); // want to report all possible errors
355-
if (isTypeNameVar && this.initialization.resolvedType != null) {
356-
if (TypeBinding.equalsEquals(TypeBinding.NULL, this.initialization.resolvedType)) {
357-
scope.problemReporter().varLocalInitializedToNull(this);
358-
variableTypeInferenceError = true;
359-
} else if (TypeBinding.equalsEquals(TypeBinding.VOID, this.initialization.resolvedType)) {
360-
scope.problemReporter().varLocalInitializedToVoid(this);
361-
variableTypeInferenceError = true;
362-
}
363-
variableType = patchType(this.initialization.resolvedType);
364-
} else {
365-
variableTypeInferenceError = true;
366-
}
367-
}
368-
}
369-
this.binding.markInitialized();
370-
if (variableTypeInferenceError) {
371-
return;
372-
}
373301
boolean resolveAnnotationsEarly = false;
374302
if (scope.environment().usesNullTypeAnnotations()
375-
&& !isTypeNameVar // 'var' does not provide a target type
303+
&& !varTypedLocal // 'var' does not provide a target type
376304
&& variableType != null && variableType.isValidBinding()) {
377305
resolveAnnotationsEarly = this.initialization instanceof Invocation
378306
|| this.initialization instanceof ConditionalExpression
@@ -387,16 +315,35 @@ public void markInitialized() {
387315
}
388316
if (this.initialization != null) {
389317
if (this.initialization instanceof ArrayInitializer) {
318+
if (varTypedLocal) {
319+
scope.problemReporter().varLocalCannotBeArrayInitalizers(this);
320+
variableType = scope.createArrayType(scope.getJavaLangObject(), 1); // Treat as array of anything
321+
}
390322
TypeBinding initializationType = this.initialization.resolveTypeExpecting(scope, variableType);
391323
if (initializationType != null) {
392324
((ArrayInitializer) this.initialization).binding = (ArrayBinding) initializationType;
393325
this.initialization.computeConversion(scope, variableType, initializationType);
394326
}
395327
} else {
396-
this.initialization.setExpressionContext(isTypeNameVar ? VANILLA_CONTEXT : ASSIGNMENT_CONTEXT);
328+
if (varTypedLocal) {
329+
this.binding.useFlag = LocalVariableBinding.ILLEGAL_SELF_REFERENCE_IF_USED; // hijack analysis phase flag
330+
if (this.initialization instanceof CastExpression castExpression)
331+
castExpression.setVarTypeDeclaration(true);
332+
}
333+
this.initialization.setExpressionContext(varTypedLocal ? VANILLA_CONTEXT : ASSIGNMENT_CONTEXT);
397334
this.initialization.setExpectedType(variableType);
398-
TypeBinding initializationType = this.initialization.resolvedType != null ? this.initialization.resolvedType : this.initialization.resolveType(scope);
335+
TypeBinding initializationType = this.initialization.resolveType(scope);
336+
if (varTypedLocal)
337+
this.binding.useFlag = LocalVariableBinding.UNUSED; // hand-over hijacked flag; let flow analysis do what it does with it.
338+
339+
if ((variableType == null && !varTypedLocal) || // having waited until any additional errors in initializer are surfaced, bail out
340+
(initializationType == null && varTypedLocal)) // fubar, bail out.
341+
return;
342+
399343
if (initializationType != null) {
344+
if (varTypedLocal) {
345+
variableType = patchType(scope, this.initialization.resolvedType);
346+
}
400347
if (TypeBinding.notEquals(variableType, initializationType)) // must call before computeConversion() and typeMismatchError()
401348
scope.compilationUnitScope().recordTypeConversion(variableType, initializationType);
402349
if (this.initialization.isConstantValueOfTypeAssignableToType(initializationType, variableType)
@@ -434,6 +381,11 @@ public void markInitialized() {
434381
this.binding.isFinal()
435382
? this.initialization.constant.castTo((variableType.id << 4) + this.initialization.constant.typeID())
436383
: Constant.NotAConstant);
384+
} else if (!isPatternVariable && !isForeachElementVariable) {
385+
if (varTypedLocal)
386+
scope.problemReporter().varLocalWithoutInitizalier(this);
387+
if (unnamedLocal)
388+
scope.problemReporter().unnamedVariableMustHaveInitializer(this);
437389
}
438390
// if init could be a constant only resolve annotation at the end, for constant to be positioned before (96991)
439391
if (!resolveAnnotationsEarly)
@@ -447,37 +399,6 @@ void validateNullAnnotations(BlockScope scope) {
447399
this.binding.tagBits &= ~TagBits.AnnotationNullMASK;
448400
}
449401

450-
/*
451-
* Checks the initializer for simple errors, and reports an error as needed. If error is found,
452-
* returns a reasonable match for further type checking.
453-
*/
454-
private TypeBinding checkInferredLocalVariableInitializer(BlockScope scope) {
455-
TypeBinding errorType = null;
456-
if (this.initialization instanceof ArrayInitializer) {
457-
scope.problemReporter().varLocalCannotBeArrayInitalizers(this);
458-
errorType = scope.createArrayType(scope.getJavaLangObject(), 1); // Treat as array of anything
459-
} else {
460-
// Catch-22: isPolyExpression() is not reliable BEFORE resolveType, so we need to peek to suppress the errors
461-
Expression polyExpression = findPolyExpression(this.initialization);
462-
if (polyExpression instanceof ReferenceExpression) {
463-
scope.problemReporter().varLocalCannotBeMethodReference(this);
464-
errorType = TypeBinding.NULL;
465-
} else if (polyExpression != null) { // Should be instanceof LambdaExpression, but this is safer
466-
scope.problemReporter().varLocalCannotBeLambda(this);
467-
errorType = TypeBinding.NULL;
468-
}
469-
}
470-
if (this.type.dimensions() > 0 || this.type.extraDimensions() > 0) {
471-
scope.problemReporter().varLocalCannotBeArray(this);
472-
errorType = scope.createArrayType(scope.getJavaLangObject(), 1); // This is just to quell some warnings
473-
}
474-
if ((this.bits & ASTNode.IsAdditionalDeclarator) != 0) {
475-
scope.problemReporter().varLocalMultipleDeclarators(this);
476-
errorType = this.initialization.resolveType(scope);
477-
}
478-
return errorType;
479-
}
480-
481402
@Override
482403
public void traverse(ASTVisitor visitor, BlockScope scope) {
483404

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,13 +1003,16 @@ public TypeBinding resolveType(BlockScope scope) {
10031003
switch (this.bits & ASTNode.RestrictiveFlagMASK) {
10041004
case Binding.VARIABLE : // =========only variable============
10051005
case Binding.VARIABLE | Binding.TYPE : //====both variable and type============
1006-
if (this.binding instanceof VariableBinding) {
1007-
VariableBinding variable = (VariableBinding) this.binding;
1006+
if (this.binding instanceof VariableBinding variable) {
10081007
TypeBinding variableType;
1009-
if (this.binding instanceof LocalVariableBinding) {
1008+
if (this.binding instanceof LocalVariableBinding localVariable) {
10101009
this.bits &= ~ASTNode.RestrictiveFlagMASK; // clear bits
10111010
this.bits |= Binding.LOCAL;
1012-
((LocalVariableBinding) this.binding).markReferenced();
1011+
if (localVariable.useFlag == LocalVariableBinding.ILLEGAL_SELF_REFERENCE_IF_USED) {
1012+
scope.problemReporter().varLocalReferencesItself(this);
1013+
localVariable.type = null;
1014+
localVariable.useFlag = LocalVariableBinding.UNUSED; // quell further errors.
1015+
}
10131016
checkLocalStaticClassVariables(scope, variable);
10141017
variableType = variable.type;
10151018
this.constant = (this.bits & ASTNode.IsStrictlyAssigned) == 0 ? variable.constant(scope) : Constant.NotAConstant;

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public boolean add(/*@NonNull*/ Expression rxpression, TypeBinding rxpressionTyp
124124

125125
this.rExpressions.add(rxpression);
126126
if (rxpressionType == null) { // tolerate poly-expression resolving to null in the absence of target type.
127-
if (!rxpression.isPolyExpression() || ((IPolyExpression) rxpression).expectedType() != null)
127+
if (!rxpression.isPolyExpression() || ((IPolyExpression) rxpression).expectedType() != null || SwitchExpression.this.expressionContext == VANILLA_CONTEXT)
128128
this.allWellFormed = false;
129129
} else if (!rxpressionType.isValidBinding()) {
130130
this.allWellFormed = false;

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypePattern.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ public TypeBinding resolveType(BlockScope scope) {
206206
return this.resolvedType;
207207

208208
Pattern enclosingPattern = this.getEnclosingPattern();
209-
if (this.local.type == null || this.local.type.isTypeNameVar(scope)) {
209+
boolean varTypedLocal = false;
210+
if (this.local.type == null || (varTypedLocal = this.local.type.isTypeNameVar(scope))) {
210211
if (enclosingPattern instanceof RecordPattern) {
211212
// 14.30.1: The type of a pattern variable declared in a nested type pattern is determined as follows ...
212213
ReferenceBinding recType = (ReferenceBinding) enclosingPattern.resolvedType;
@@ -223,9 +224,11 @@ public TypeBinding resolveType(BlockScope scope) {
223224
this.local.type.resolvedType = this.resolvedType;
224225
}
225226
}
227+
} else if (varTypedLocal) {
228+
this.local.type.resolveType(scope, true); // trigger complaint
226229
}
227230
}
228-
this.local.resolve(scope, true);
231+
this.local.resolve(scope);
229232
if (this.local.binding != null) {
230233
this.local.binding.modifiers |= ExtraCompilerModifiers.AccOutOfFlowScope; // start out this way, will be BlockScope.include'd when definitely assigned
231234
CompilerOptions compilerOptions = scope.compilerOptions();

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public void resolve(BlockScope scope) {
141141

142142
if (this.switchExpression == null) {
143143
this.switchExpression = enclosingSwitchExpression(scope);
144-
if (this.switchExpression != null && this.switchExpression.isPolyExpression()) {
144+
if (this.switchExpression != null) {
145145
this.expression.setExpressionContext(this.switchExpression.expressionContext); // result expressions feature in same context ...
146146
this.expression.setExpectedType(this.switchExpression.expectedType); // ... with the same target type
147147
}

0 commit comments

Comments
 (0)