Skip to content

Commit

Permalink
fine-tuning for field access:
Browse files Browse the repository at this point in the history
+ central new Reference.checkFieldAccessInEarlyConstructionContext()
  - invoked from SingleNameReference & FieldReference
+ new errors: assign super field, assign field with initializer
+ use enterEarlyConstructionContext() for args of first ctor call, too
+ fine tuning of use of enablement guards: where to check / report?
  • Loading branch information
stephan-herrmann committed Jul 14, 2024
1 parent 27ccd13 commit d2e82ac
Show file tree
Hide file tree
Showing 17 changed files with 272 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2663,4 +2663,16 @@ public interface IProblem {
* @noreference preview feature
*/
int DuplicateExplicitConstructorCall = PreviewRelated + 2028;

/**
* @since 3.39
* @noreference preview feature
*/
int SuperFieldAssignInEarlyConstructionContext = PreviewRelated + 2029;

/**
* @since 3.39
* @noreference preview feature
*/
int AssignFieldWithInitializerInEarlyConstructionContext = PreviewRelated + 2030;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.JavaFeature;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
Expand Down Expand Up @@ -539,12 +540,13 @@ public TypeBinding resolveType(BlockScope scope) {
this.binding.getTypeAnnotations() != Binding.NO_ANNOTATIONS) {
this.resolvedType = scope.environment().createAnnotatedType(this.resolvedType, this.binding.getTypeAnnotations());
}
checkPreConstructorContext(scope);
checkEarlyConstructionContext(scope);
return this.resolvedType;
}

protected void checkPreConstructorContext(BlockScope scope) {
if (this.type != null && this.type.resolvedType instanceof ReferenceBinding currentType) {
protected void checkEarlyConstructionContext(BlockScope scope) {
if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(scope.compilerOptions())
&& this.type != null && this.type.resolvedType instanceof ReferenceBinding currentType) {
TypeBinding uninitialized = scope.getMatchingUninitializedType(currentType, !currentType.isLocalType());
if (uninitialized != null)
scope.problemReporter().allocationInEarlyConstructionContext(this, this.resolvedType, uninitialized);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,13 @@ public void resolveStatements() {
this.scope.problemReporter().recordMissingExplicitConstructorCallInNonCanonicalConstructor(this);
this.constructorCall = null;
} else {
ExplicitConstructorCall lateConstructorCall = getLateConstructorCall();
ExplicitConstructorCall lateConstructorCall = null;
if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.matchesCompliance(this.scope.compilerOptions())) {
this.scope.enterEarlyConstructionContext(); // even if no late ctor call to also capture arguments of ctor call as 1st stmt
lateConstructorCall = getLateConstructorCall();
}
if (lateConstructorCall != null) {
this.constructorCall = null; // not used with JEP 482, conversely, constructorCall!=null signals no JEP 482 context
this.scope.enterEarlyConstructionContext();
if (!sourceType.isRecord()) { // explicit constructor call is never legal for records
this.scope.problemReporter().validateJavaFeatureSupport(JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES,
lateConstructorCall.sourceStart,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,15 @@ public void resolve(BlockScope scope) {
ConstructorDeclaration constructorDeclaration = (ConstructorDeclaration) methodDeclaration;
if (methodDeclaration == null || !methodDeclaration.isConstructor()) {
hasError = true;
} else if (constructorDeclaration.constructorCall != this) {
isFirstStatement = false;
hasError = !scope.isInsideEarlyConstructionContext(null, false);
} else {
ExplicitConstructorCall constructorCall = constructorDeclaration.constructorCall;
if (constructorCall == null) {
constructorCall = constructorDeclaration.getLateConstructorCall();
}
if (constructorCall != null && constructorCall != this) {
isFirstStatement = false;
hasError = true;
}
}
if (hasError) {
if (!(methodDeclaration instanceof CompactConstructorDeclaration)) {// already flagged for CCD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,9 @@ public TypeBinding resolveType(BlockScope scope) {
}
// the case receiverType.isArrayType and token = 'length' is handled by the scope API
FieldBinding fieldBinding = this.binding = scope.getField(this.actualReceiverType, this.token, this);
if (this.receiver instanceof ThisReference) {
checkFieldAccessInEarlyConstructionContext(scope, fieldBinding.name, fieldBinding, this.actualReceiverType);
}
if (!fieldBinding.isValidBinding()) {
this.constant = Constant.NotAConstant;
if (this.receiver.resolvedType instanceof ProblemReferenceBinding) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public TypeBinding resolveType(BlockScope scope) {
this.resolvedType = scope.environment().createAnnotatedType(this.resolvedType, this.binding.getTypeAnnotations());
}
}
checkPreConstructorContext(scope);
checkEarlyConstructionContext(scope);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.JavaFeature;
import org.eclipse.jdt.internal.compiler.lookup.*;

public class QualifiedThisReference extends ThisReference {
Expand Down Expand Up @@ -121,17 +122,14 @@ public TypeBinding resolveType(BlockScope scope) {
}

// Ensure one cannot write code like: B() { super(B.this); }
if (depth == 0) {
if (depth == 0 || JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(scope.compilerOptions())) {
checkAccess(scope, null);
} // if depth>0, path emulation will diagnose bad scenarii
} // if depth>0, prior to JEP 482: path emulation will diagnose bad scenarii
else if (scope.compilerOptions().complianceLevel >= ClassFileConstants.JDK16) {
MethodScope ms = scope.methodScope();
if (ms.isStatic)
ms.problemReporter().errorThisSuperInStatic(this);
}
if (scope.isInsideEarlyConstructionContext(this.resolvedType, false)) {
scope.problemReporter().errorExpressionInEarlyConstructionContext(this);
}
MethodScope methodScope = scope.namedMethodScope();
if (methodScope != null) {
MethodBinding method = methodScope.referenceMethodBinding();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.JavaFeature;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
Expand Down Expand Up @@ -193,6 +194,40 @@ void reportOnlyUselesslyReadPrivateField(BlockScope currentScope, FieldBinding f
}
}
}

protected void checkFieldAccessInEarlyConstructionContext(BlockScope scope, char[] token, FieldBinding fieldBinding, TypeBinding actualReceiverType) {
if (actualReceiverType != null && JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.matchesCompliance(scope.compilerOptions())) {
if (scope.isInsideEarlyConstructionContext(actualReceiverType, false)) {
// §6.5.6.1 (JEP 482):
// If the declaration denotes an instance variable of a class C ... then .. or a compile time occurs:
// - [...]
// - If the expression name appears in an early construction context of C (8.8.7.1),
// then it is the left-hand operand of a simple assignment expression (15.26),
// and the declaration of the named variable lacks an initializer.
if ((this.bits & ASTNode.IsStrictlyAssigned) == 0) {
// Error: not 'left-hand operand of a simple assignment expression'
if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(scope.compilerOptions())) {
scope.problemReporter().fieldReadInEarlyConstructionContext(token, this.sourceStart, this.sourceEnd);
}
// otherwise we leave it to later phase to detect if required enclosing instance is available
return;
} else if (TypeBinding.notEquals(fieldBinding.declaringClass, scope.enclosingReceiverType())) {
// Error: not field of class C
scope.problemReporter().superFieldAssignInEarlyConstructionContext(this, fieldBinding);
return;
} else {
FieldDeclaration sourceField = fieldBinding.sourceField();
if (sourceField != null && sourceField.initialization != null) {
// Error: field has an initializer
scope.problemReporter().assignFieldWithInitializerInEarlyConstructionContext(token, this.sourceStart, this.sourceEnd);
return;
}
}
// otherwise legal if JEP 482 is enabled
scope.problemReporter().validateJavaFeatureSupport(JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES, this.sourceStart, this.sourceEnd);
}
}
}
/* report a local/arg that is only read from a 'special operator',
* i.e., in a postIncrement expression or a compound assignment,
* where the information is never flowing out off the local/arg. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public void resolve(BlockScope scope) {
if (methodBinding != null && methodBinding.isCompactConstructor())
scope.problemReporter().recordCompactConstructorHasReturnStatement(this);

if (scope.isInsideEarlyConstructionContext(null, false))
if (lambda == null && scope.isInsideEarlyConstructionContext(null, false))
scope.problemReporter().errorReturnInEarlyConstructionContext(this);

if (this.expression != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,7 @@ public TypeBinding checkFieldAccess(BlockScope scope) {
if (scope.compilerOptions().getSeverity(CompilerOptions.UnqualifiedFieldAccess) != ProblemSeverities.Ignore) {
scope.problemReporter().unqualifiedFieldAccess(this, fieldBinding);
}
if (this.actualReceiverType != null
&& scope.isInsideEarlyConstructionContext(this.actualReceiverType, false)
&& (this.bits & ASTNode.IsStrictlyAssigned) == 0) {
scope.problemReporter().fieldReadInEarlyConstructionContext(this.token, this.sourceStart, this.sourceEnd);
}
checkFieldAccessInEarlyConstructionContext(scope, this.token, fieldBinding, this.actualReceiverType);
// must check for the static status....
if (methodScope.isStatic) {
scope.problemReporter().staticFieldAccessToNonStaticVariable(this, fieldBinding);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,18 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte
}

public boolean checkAccess(BlockScope scope, ReferenceBinding receiverType) {
boolean isAssigment = (this.bits & ASTNode.IsStrictlyAssigned) != 0;
MethodScope methodScope = scope.methodScope();
if (scope.isInsideEarlyConstructionContext(null, false)) {
// JEP 482
if (!isAssigment) {
scope.problemReporter().errorExpressionInEarlyConstructionContext(this);
return false;
}
} else {
// old style: this/super cannot be used in constructor call
if (methodScope.isConstructorCall) {
if (isAssigment) {
if (scope.problemReporter().validateJavaFeatureSupport(JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES, this.sourceStart, this.sourceEnd))
return false;
} else {
if ((this.bits & ASTNode.IsStrictlyAssigned) == 0) { // checking assignments is deferred to Reference.checkFieldAccessInEarlyConstructionContext()
if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(scope.compilerOptions())) {
if (!this.inFieldReference // this.f is also covered in Reference.checkFieldAccessInEarlyConstructionContext()
&& scope.isInsideEarlyConstructionContext(this.resolvedType, false)) {
// JEP 482 message
scope.problemReporter().errorExpressionInEarlyConstructionContext(this);
return false;
}
} else {
if (methodScope.isConstructorCall) {
// old style: this/super cannot be used in constructor call
methodScope.problemReporter().fieldsOrThisBeforeConstructorInvocation(this);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public class ClassScope extends Scope {
public TypeReference superTypeReference;
java.util.ArrayList<TypeReference> deferredBoundChecks;
public boolean resolvingPolyExpressionArguments = false;
// temporarily set while processing statements in an early construction context of this type:
/**
* This is the primary flag for detection of early construction contexts (JEP 482).
* It is temporarily set on the scope of a class while processing statements of this class's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2150,8 +2150,8 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,
if (invocationSite instanceof ASTNode node
&& (node.bits & ASTNode.IsStrictlyAssigned) != 0
&& JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.matchesCompliance(compilerOptions())) {
problemReporter().validateJavaFeatureSupport(JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES, invocationSite.sourceStart(), invocationSite.sourceEnd());
} else {
// enablement check for assignment deferred to Reference.checkFieldAccessInEarlyConstructionContext()
} else if (!JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(compilerOptions())) {
insideProblem =
new ProblemFieldBinding(
fieldBinding, // closest match
Expand Down Expand Up @@ -5699,13 +5699,18 @@ public boolean isInsideEarlyConstructionContext(TypeBinding targetClass, boolean
}

public TypeBinding getMatchingUninitializedType(TypeBinding targetClass, boolean considerEnclosings) {
if (targetClass == null)
if (targetClass == null) {
targetClass = enclosingReceiverType();
} else if (!(targetClass instanceof ReferenceBinding)) {
return null;
}
ClassScope currentEnclosing = classScope();
while (currentEnclosing != null) {
SourceTypeBinding enclosingType = currentEnclosing.referenceContext.binding;
TypeBinding currentTarget = targetClass;
while (currentTarget != null) {
if (currentTarget instanceof ParameterizedTypeBinding)
currentTarget = currentTarget.actualType();
if (TypeBinding.equalsEquals(enclosingType, currentTarget)) {
if (currentEnclosing.insideEarlyConstructionContext)
return enclosingType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12649,6 +12649,23 @@ public void fieldReadInEarlyConstructionContext(char[] token, int sourceStart, i
sourceStart,
sourceEnd);
}
public void superFieldAssignInEarlyConstructionContext(ASTNode location, FieldBinding field) {
this.handle(
IProblem.SuperFieldAssignInEarlyConstructionContext,
new String[] {String.valueOf(field.name), String.valueOf(field.declaringClass.readableName())},
new String[] {String.valueOf(field.name), String.valueOf(field.declaringClass.shortReadableName())},
location.sourceStart,
location.sourceEnd);
}
public void assignFieldWithInitializerInEarlyConstructionContext(char[] token, int sourceStart, int sourceEnd) {
String[] arguments = new String[] {String.valueOf(token)};
this.handle(
IProblem.AssignFieldWithInitializerInEarlyConstructionContext,
arguments,
arguments,
sourceStart,
sourceEnd);
}
public void errorReturnInEarlyConstructionContext(Statement stmt) {
String[] arguments = new String[] {stmt.toString()};
this.handle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,8 @@
2026 = Cannot instantiate class {0} in an early construction context of class {1}
2027 = Cannot invoke method {0}() in an early construction context
2028 = Constructor cannot have more than one explicit constructor call
2029 = Cannot assign field ''{0}'' from class ''{1}'' in an early construction context
2030 = Cannot assign field ''{0}'' in an early construction context, because it has an initializer

### ELABORATIONS
## Access restrictions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,8 @@ class ProblemAttributes {
expectedProblemAttributes.put("MessageSendInEarlyConstructionContext", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("DisallowedStatementInEarlyConstructionContext", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("DuplicateExplicitConstructorCall", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("SuperFieldAssignInEarlyConstructionContext", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("AssignFieldWithInitializerInEarlyConstructionContext", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED));
expectedProblemAttributes.put("NamedPatternVariablesDisallowedHere", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("OperandStackExceeds64KLimit", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
expectedProblemAttributes.put("OperandStackSizeInappropriate", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
Expand Down Expand Up @@ -2481,6 +2483,8 @@ class ProblemAttributes {
expectedProblemAttributes.put("MessageSendInEarlyConstructionContext", SKIP);
expectedProblemAttributes.put("DisallowedStatementInEarlyConstructionContext", SKIP);
expectedProblemAttributes.put("DuplicateExplicitConstructorCall", SKIP);
expectedProblemAttributes.put("SuperFieldAssignInEarlyConstructionContext", SKIP);
expectedProblemAttributes.put("AssignFieldWithInitializerInEarlyConstructionContext", SKIP);
expectedProblemAttributes.put("NamedPatternVariablesDisallowedHere", SKIP);
expectedProblemAttributes.put("OperandStackExceeds64KLimit", SKIP);
expectedProblemAttributes.put("OperandStackSizeInappropriate", SKIP);
Expand Down
Loading

0 comments on commit d2e82ac

Please sign in to comment.