Skip to content

Commit

Permalink
Support parsing and generating unnamed variables and patterns
Browse files Browse the repository at this point in the history
Doesn't include support for lists of patterns in case labels,
that will be handled in a future PR.

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 authored and rgrunber committed Jan 18, 2024
1 parent a8d8033 commit 4929232
Show file tree
Hide file tree
Showing 57 changed files with 3,138 additions and 2,147 deletions.
46 changes: 37 additions & 9 deletions org.eclipse.jdt.core.compiler.batch/grammar/java.g
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ $Terminals
BeginCaseElement
RestrictedIdentifierWhen
BeginRecordPattern
UNDERSCORE

-- BodyMarker

Expand Down Expand Up @@ -181,6 +182,7 @@ $Alias
'...' ::= ELLIPSIS
'@308' ::= AT308
'@308...' ::= AT308DOTDOTDOT
'_' ::= UNDERSCORE

$Start
Goal
Expand Down Expand Up @@ -835,6 +837,8 @@ RestoreDiet ::= $empty
VariableDeclaratorId ::= 'Identifier' Dimsopt
/:$readableName VariableDeclaratorId:/
/:$recovery_template Identifier:/
VariableDeclaratorId ::= '_'
/.$putCase consumeUnnamedVariable(); $break ./

VariableInitializer -> Expression
VariableInitializer -> ArrayInitializer
Expand Down Expand Up @@ -1263,6 +1267,10 @@ Pattern -> RecordPattern
TypePattern ::= Modifiersopt Type 'Identifier'
/.$putCase consumeTypePattern(); $break ./
/:$readableName TypePattern:/
TypePattern ::= Modifiersopt Type '_'
/.$putCase consumeTypePattern(); $break ./
/:$readableName TypePattern:/
/:$compliance 21:/

-----------------------------------------------
-- 16 feature : end of instanceof pattern matching
Expand All @@ -1272,26 +1280,30 @@ TypePattern ::= Modifiersopt Type 'Identifier'
-- 20 preview feature : record patterns
-----------------------------------------------

RecordPattern ::= Modifiersopt ReferenceType PushLPAREN PatternListopt PushRPAREN
RecordPattern ::= Modifiersopt ReferenceType PushLPAREN ComponentPatternListopt PushRPAREN
/.$putCase consumeRecordPattern(); $break ./
/:$readableName RecordPattern:/
/:$compliance 20:/

PatternListopt ::= $empty
ComponentPatternListopt ::= $empty
/.$putCase consumePatternListopt(); $break ./
/:$readableName PatternListopt:/
/:$readableName ComponentPatternListopt:/
/:$compliance 20:/

PatternListopt -> PatternList
ComponentPatternListopt -> ComponentPatternList
/:$readableName PatternListopt:/
/:$compliance 20:/

PatternList -> Pattern
PatternList ::= PatternList ',' Pattern
ComponentPatternList -> ComponentPattern
ComponentPatternList ::= ComponentPatternList ',' ComponentPattern
/.$putCase consumePatternList(); $break ./
/:$readableName PatternList:/
/:$readableName ComponentPatternList:/
/:$compliance 20:/

ComponentPattern -> Pattern
ComponentPattern -> UnnamedPattern
/:$compliance 21:/

-----------------------------------------------
-- 20 preview feature : end of record patterns
-----------------------------------------------
Expand Down Expand Up @@ -1323,6 +1335,11 @@ StringTemplateExpression ::= Primary '.' TemplateArgument
-- 21 preview feature : end of String templates
-----------------------------------------------

UnnamedPattern ::= '_'
/.$putCase consumeUnnamedPattern(); $break ./
/:$readableName UnnamedPattern:/
/:$compliance 21:/

ConstantDeclaration -> FieldDeclaration
/:$readableName ConstantDeclaration:/

Expand Down Expand Up @@ -1898,6 +1915,11 @@ NestedLambda ::= $empty
/.$putCase consumeNestedLambda(); $break ./
/:$readableName NestedLambda:/

LambdaParameters ::= '_' NestedLambda
/.$putCase consumeTypeElidedLambdaParameter(false); $break ./
/:$readableName TypeElidedUnnamedFormalParameter:/
/:$compliance 21:/

LambdaParameters ::= Identifier NestedLambda
/.$putCase consumeTypeElidedLambdaParameter(false); $break ./
/:$readableName TypeElidedFormalParameter:/
Expand Down Expand Up @@ -1931,6 +1953,11 @@ TypeElidedFormalParameter ::= Modifiersopt Identifier
/:$readableName TypeElidedFormalParameter:/
/:$compliance 1.8:/

TypeElidedFormalParameter ::= '_'
/.$putCase consumeBracketedTypeElidedUnderscoreLambdaParameter(); $break ./
/:$readableName TypeElidedFormalParameter:/
/:$compliance 21:/

-- A lambda body of the form x is really '{' return x; '}'
LambdaBody -> ElidedLeftBraceAndReturn Expression ElidedSemicolonAndRightBrace
LambdaBody -> Block
Expand Down Expand Up @@ -2515,11 +2542,11 @@ EnhancedForStatementNoShortIf ::= EnhancedForStatementHeader StatementNoShortIf
/.$putCase consumeEnhancedForStatement(); $break ./
/:$readableName EnhancedForStatementNoShortIf:/

EnhancedForStatementHeaderInit ::= 'for' '(' Type PushModifiers Identifier Dimsopt
EnhancedForStatementHeaderInit ::= 'for' '(' Type PushModifiers VariableDeclaratorId
/.$putCase consumeEnhancedForStatementHeaderInit(false); $break ./
/:$readableName EnhancedForStatementHeaderInit:/

EnhancedForStatementHeaderInit ::= 'for' '(' Modifiers Type PushRealModifiers Identifier Dimsopt
EnhancedForStatementHeaderInit ::= 'for' '(' Modifiers Type PushRealModifiers VariableDeclaratorId
/.$putCase consumeEnhancedForStatementHeaderInit(true); $break ./
/:$readableName EnhancedForStatementHeaderInit:/

Expand Down Expand Up @@ -3203,6 +3230,7 @@ AT308DOTDOTDOT ::= '@'
ELLIPSIS ::= '...'
ARROW ::= '->'
COLON_COLON ::= '::'
UNDERSCORE ::= '_'

$end
-- need a carriage return after the $end
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] == '_') || ("\\u005F".equals(this.name.toString()))) //$NON-NLS-1$
&& 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 @@ -484,7 +498,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 @@ -39,6 +39,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 @@ -48,7 +51,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 @@ -197,7 +197,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 @@ -227,14 +227,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 @@ -54,7 +53,7 @@ public void collectPatternVariablesToScope(LocalVariableBinding[] variables, Blo
if (this.resolvedType == null) {
this.resolveType(scope);
}
if (this.local != null && this.local.binding != null) {
if (this.local != null && this.local.binding != null && !this.local.isUnnamed(scope)) {
LocalVariableBinding binding = this.local.binding;
if (variables != null) {
for (LocalVariableBinding variable : variables) {
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 @@ -288,7 +288,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
Loading

0 comments on commit 4929232

Please sign in to comment.