Skip to content

Commit

Permalink
Unnamed patterns and varibles (JEP 443) without pattern lists
Browse files Browse the repository at this point in the history
Contains support for unnamed patterns and variables (JEP443),
including parsing, analysis and code generation,
but omits support for lists of patterns in case statements,
which will be handled in a future PR.

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 committed Jan 26, 2024
1 parent 06c7a67 commit 22fcccc
Show file tree
Hide file tree
Showing 57 changed files with 2,532 additions and 2,117 deletions.
222 changes: 125 additions & 97 deletions org.eclipse.jdt.core.compiler.batch/grammar/java.g

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2581,4 +2581,15 @@ public interface IProblem {
* @since 3.35
*/
int SyntheticAccessorNotEnclosingMethod = MethodRelated + 1990;

/**
* @since 3.37
* @noreference preview feature
*/
int UnderscoreCannotBeUsedHere = PreviewRelated + 2000;
/**
* @since 3.37
* @noreference preview feature
*/
int UnnamedVariableMustHaveInitializer = PreviewRelated + 2001;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
Expand Down Expand Up @@ -144,4 +145,15 @@ public void setDepth(int depth) {
public void setFieldIndex(int depth) {
// do nothing by default
}

/**
* Returns true if this variable is an unnamed variable (_) and false otherwise.
*
* @param scope used to determine source level
*/
public boolean isUnnamed(BlockScope scope) {
return this.name.length == 1 && this.name[0] == '_'
&& scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK21
&& scope.compilerOptions().enablePreviewFeatures;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,25 @@ private Annotation[] getCorrespondingRecordComponentAnnotationsIfApplicable(Abst
return null;
}
public TypeBinding bind(MethodScope scope, TypeBinding typeBinding, boolean used) {
if (this.isUnnamed(scope) && !scope.isLambdaScope()) {
scope.problemReporter().illegalUseOfUnderscoreAsAnIdentifier(this.sourceStart, this.sourceEnd, scope.compilerOptions().sourceLevel > ClassFileConstants.JDK1_8, true);
}

TypeBinding newTypeBinding = createBinding(scope, typeBinding); // basically a no-op if createBinding() was called before

// record the resolved type into the type reference
Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
if (existingVariable != null && existingVariable.isValidBinding()){
final boolean localExists = existingVariable instanceof LocalVariableBinding;
if (localExists && this.hiddenVariableDepth == 0) {
if ((this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope()) {
scope.problemReporter().lambdaRedeclaresArgument(this);
} else if (scope.referenceContext instanceof CompactConstructorDeclaration) {
// skip error reporting - hidden params - already reported in record components
} else {
scope.problemReporter().redefineArgument(this);
if (!this.isUnnamed(scope)) {
if ((this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope()) {
scope.problemReporter().lambdaRedeclaresArgument(this);
} else if (scope.referenceContext instanceof CompactConstructorDeclaration) {
// skip error reporting - hidden params - already reported in record components
} else {
scope.problemReporter().redefineArgument(this);
}
}
} else {
boolean isSpecialArgument = false;
Expand Down Expand Up @@ -234,7 +240,7 @@ public TypeBinding resolveForCatch(BlockScope scope) {
}
}
Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
if (existingVariable != null && existingVariable.isValidBinding()){
if (existingVariable != null && existingVariable.isValidBinding() && !isUnnamed(scope)) {
if (existingVariable instanceof LocalVariableBinding && this.hiddenVariableDepth == 0) {
scope.problemReporter().redefineArgument(this);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ public StringBuilder printStatement(int indent, StringBuilder output) {
}

public void resolve(MethodScope initializationScope) {
if (this.isUnnamed(initializationScope)) {
initializationScope.problemReporter().illegalUseOfUnderscoreAsAnIdentifier(this.sourceStart, this.sourceEnd, initializationScope.compilerOptions().sourceLevel > ClassFileConstants.JDK1_8, true);
}

// the two <constant = Constant.NotAConstant> could be regrouped into
// a single line but it is clearer to have two lines while the reason of their
// existence is not at all the same. See comment for the second one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,24 @@
import java.util.Set;

import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationCollector;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.codegen.AnnotationContext;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBindingVisitor;
import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner;

public class LocalDeclaration extends AbstractVariableDeclaration {
Expand Down Expand Up @@ -139,7 +151,6 @@ public void checkModifiers() {
*/
@Override
public void generateCode(BlockScope currentScope, CodeStream codeStream) {

// even if not reachable, variable must be added to visible if allocated (28298)
if (this.binding.resolvedPosition != -1) {
codeStream.addVisibleLocalVariable(this.binding);
Expand Down Expand Up @@ -260,10 +271,13 @@ private static Expression findPolyExpression(Expression e) {
public void resolve(BlockScope scope) {
resolve(scope, false);
}
public void resolve(BlockScope scope, boolean isPatternVariable) {
// prescan NNBD
public void resolve(BlockScope scope, boolean isPatternVariable) { // prescan NNBD
handleNonNullByDefault(scope, this.annotations, this);

if (!isPatternVariable && (this.bits & ASTNode.IsForeachElementVariable) == 0 && this.initialization == null && this.isUnnamed(scope)) {
scope.problemReporter().unnamedVariableMustHaveInitializer(this);
}

TypeBinding variableType = null;
boolean variableTypeInferenceError = false;
boolean isTypeNameVar = isTypeNameVar(scope);
Expand Down Expand Up @@ -301,7 +315,7 @@ public void resolve(BlockScope scope, boolean isPatternVariable) {
}

Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
if (existingVariable != null && existingVariable.isValidBinding()){
if (existingVariable != null && existingVariable.isValidBinding() && !this.isUnnamed(scope)) {
boolean localExists = existingVariable instanceof LocalVariableBinding;
if (localExists && (this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope() && this.hiddenVariableDepth == 0) {
scope.problemReporter().lambdaRedeclaresLocal(this);
Expand Down Expand Up @@ -488,7 +502,9 @@ public void traverse(ASTVisitor visitor, BlockScope scope) {
for (int i = 0; i < annotationsLength; i++)
this.annotations[i].traverse(visitor, scope);
}
this.type.traverse(visitor, scope);
if (this.type != null) {
this.type.traverse(visitor, scope);
}
if (this.initialization != null)
this.initialization.traverse(visitor, scope);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public abstract class Pattern extends Expression {

public int nestingLevel = 0;

// denotes index of this pattern in the parent record pattern, or -1 for patterns whose parent is not a record pattern
public int index = -1;

@Override
public boolean containsPatternVariable() {
class PatternVariablesVisitor extends ASTVisitor {
Expand All @@ -50,7 +53,7 @@ class PatternVariablesVisitor extends ASTVisitor {
@Override
public boolean visit(TypePattern typePattern, BlockScope blockScope) {
this.hasPatternVar = typePattern.local != null;
this.typeElidedVar |= typePattern.getType().isTypeNameVar(blockScope);
this.typeElidedVar |= typePattern.getType() == null || typePattern.getType().isTypeNameVar(blockScope);
return !(this.hasPatternVar && this.typeElidedVar);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void setAccessorsPlusInfuseInferredType(BlockScope scope) {
continue;
TypePattern tp = (TypePattern) p;
RecordComponentBinding componentBinding = components[i];
if (p.getType().isTypeNameVar(scope)) {
if (p.getType() == null || p.getType().isTypeNameVar(scope)) {
infuseInferredType(scope, tp, componentBinding);
if (tp.local.binding != null) // rewrite with the inferred type
tp.local.binding.type = componentBinding.type;
Expand Down Expand Up @@ -216,14 +216,26 @@ private boolean shouldInitiateRecordTypeInference() {
return this.resolvedType != null && this.resolvedType.isRawType();
}
private void infuseInferredType(Scope currentScope, TypePattern tp, RecordComponentBinding componentBinding) {
SingleTypeReference ref = new SingleTypeReference(tp.local.type.getTypeName()[0],
tp.local.type.sourceStart,
tp.local.type.sourceEnd) {
@Override
public TypeBinding resolveType(BlockScope scope, boolean checkBounds) {
return componentBinding.type;
}
};
SingleTypeReference ref;
if (tp.local.type == null) {
ref = new SingleTypeReference("var".toCharArray(), //$NON-NLS-1$
tp.local.sourceStart,
tp.local.sourceEnd) {
@Override
public TypeBinding resolveType(BlockScope scope, boolean checkBounds) {
return componentBinding.type;
}
};
} else {
ref = new SingleTypeReference(tp.local.type.getTypeName()[0],
tp.local.type.sourceStart,
tp.local.type.sourceEnd) {
@Override
public TypeBinding resolveType(BlockScope scope, boolean checkBounds) {
return componentBinding.type;
}
};
}
tp.local.type = ref;
if (componentBinding.type != null && (componentBinding.tagBits & TagBits.HasMissingType) != 0) {
currentScope.problemReporter().invalidType(ref, componentBinding.type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public class TypePattern extends Pattern {

public LocalDeclaration local;
Expression expression;
public int index = -1; // denoting position

public TypePattern(LocalDeclaration local) {
this.local = local;
Expand All @@ -51,7 +50,7 @@ public TypeReference getType() {
}
@Override
public LocalVariableBinding[] bindingsWhenTrue() {
return this.local != null && this.local.binding != null ?
return this.local != null && this.local.binding != null && !this.local.isUnnamed(this.local.binding.declaringScope) ?
new LocalVariableBinding[] { this.local.binding } : NO_VARIABLES;
}
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public enum JavaFeature {
new char[][] {},
false),
UNNAMMED_PATTERNS_AND_VARS(ClassFileConstants.JDK21,
Messages.bind(Messages.unnammed_patterns_and_vars),
Messages.bind(Messages.unnamed_patterns_and_vars),
new char[][] {},
true),
UNNAMMED_CLASSES_AND_INSTANCE_MAIN_METHODS(ClassFileConstants.JDK21,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ void computeLocalVariablePositions(int ilocal, int initOffset, CodeStream codeSt
// do not report fake used variable
if (local.useFlag == LocalVariableBinding.UNUSED
&& (local.declaration != null) // unused (and non secret) local
&& ((local.declaration.bits & ASTNode.IsLocalDeclarationReachable) != 0)) { // declaration is reachable
&& ((local.declaration.bits & ASTNode.IsLocalDeclarationReachable) != 0) // declaration is reachable
&& !local.declaration.isUnnamed(local.declaringScope)) {

if (local.isCatchParameter()) {
problemReporter().unusedExceptionParameter(local.declaration); // report unused catch arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ records = Records
sealed_types = Sealed Types
pattern_matching_switch = Pattern Matching in Switch
record_patterns = Record Pattern
unnammed_patterns_and_vars = Unnammed Patterns and Variables
unnamed_patterns_and_vars = Unnamed Patterns and Variables
unnamed_classes_and_instance_main_methods = Unnamed Classes and Instance Main Methods
string_templates = String Template

0 comments on commit 22fcccc

Please sign in to comment.