diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index f97f2ff3a1..82285abb8e 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -503,6 +503,14 @@ public class JavaParser { } } + // make the top node a child of the second node on the stack + private void injectTop() { + AbstractJavaNode top = (AbstractJavaNode) jjtree.popNode(); + AbstractJavaNode prev = (AbstractJavaNode) jjtree.peekNode(); + prev.jjtAddChild(top, prev.jjtGetNumChildren()); + prev.jjtSetLastToken(top.jjtGetLastToken()); + } + } PARSER_END(JavaParser) @@ -2101,7 +2109,8 @@ void ClassOrInterfaceType() #void: be sure that the last segment is a type name. */ - ( AmbiguousName() [ TypeArguments() ]) #ClassOrInterfaceType(>1) + AmbiguousName() + [ TypeArguments() #ClassOrInterfaceType(2) ] { // At this point the first ClassOrInterfaceType may be on top of the stack, // but its image is not set. If it is on the stack we need to shrink the bounds @@ -2122,18 +2131,19 @@ void ClassOrInterfaceType() #void: executes a loop. That scheme preserves the position of type arguments and annotations. See #1150. */ - ( LOOKAHEAD(2) - ( - "." - (TypeAnnotation())* - { jjtThis.setImage(getToken(0).getImage()); } - {jjtree.extendLeft();} // We'll enclose the previous segment - [ LOOKAHEAD( "<" ) TypeArguments() ] - ) #ClassOrInterfaceType - )* + ( LOOKAHEAD(2) "." ClassTypeSegment() )* { forceTypeContext(); } } +private void ClassTypeSegment() #ClassOrInterfaceType: +{} +{ + TypeAnnotationList() + + // We'll enclose the previous segment + { setLastTokenImage(jjtThis); jjtree.extendLeft();} + [ TypeArguments() ] +} void TypeArguments(): {} @@ -2478,31 +2488,57 @@ void PrimaryPrefix() #void : Literal() | "this" #ThisExpression | "super" #SuperExpression +| UnqualifiedAllocationExpr() +| ("void" "." "class") #ClassLiteral +| (PrimitiveType() [ Dims() ] ) #ArrayType(>1) + ( + MethodReference() + | "." "class" #ClassLiteral(1) + ) + + // todo we can probably simplify the lambda lookaheads | LOOKAHEAD( "->", {!inSwitchLabel} ) LambdaExpression() | LOOKAHEAD( "(" ( "," )* ")" "->" , {!inSwitchLabel} ) LambdaExpression() | LOOKAHEAD( LambdaParameterList() "->", {!inSwitchLabel} ) LambdaExpression() + + | ("(" Expression() ")") #ParenthesizedExpression -| UnqualifiedAllocationExpr() -| LOOKAHEAD( TypeOrVoid() "." "class" ) - (TypeOrVoid() "." "class") #ClassLiteral -| LOOKAHEAD( ReferenceType() "::" [TypeArguments()] "new" ) - ReferenceType() MethodReference() +//| LOOKAHEAD( TypeOrVoid() "." "class" ) +// (TypeOrVoid() "." "class") #ClassLiteral +//| LOOKAHEAD( ReferenceType() "::" [TypeArguments()] "new" ) +// ReferenceType() MethodReference() // not a constructor reference, and is just a sequence of identifiers: could be an expression or a type -| LOOKAHEAD( Name() "::" ) - AmbiguousName() MethodReference() +//| LOOKAHEAD( Name() "::" ) +// AmbiguousName() MethodReference() // we know it's not just a sequence of identifiers, so if // ReferenceType matches, there are type parameters or annots, // which means we shouldn't parse as an ambiguous name otherwise // we choke on the type parameters -| LOOKAHEAD( ReferenceType() "::" ) - ReferenceType() MethodReference() -| AmbiguousName() [ LOOKAHEAD("." "super" ".") - // The LHS of this or super is necessarily a type name, ie ambiguous - // Having this here instead of in PrimarySuffix makes the grammar more - // restrictive and also allows ExplicitConstructorInvocation to be parsed - "." "super" #SuperExpression(1) MemberSelector() - ] +//| LOOKAHEAD( ReferenceType() "::" ) +// ReferenceType() MethodReference() +| AmbiguousName() [ PrimaryStep2() ] +} + +// Step right after the *first* ambiguous name if any +// then we have more options than a primary suffix, +// we can still expect the rest of a type name + +void PrimaryStep2() #void: +{} +{ + {forceTypeContext();} TypeArguments() {injectTop();} ( "." ClassTypeSegment() )* MethodReference() + | MethodReference() + | ArgumentList() #MethodCall(2) + | "." ( + {forceTypeContext();} "class" #ClassLiteral(1) + | "this" #ThisExpression(1) + | MemberSelector() + // we need to lookahead for the dot after "super", because in qualified + // super constructor invocation the "super" might be followed by a "(" + | LOOKAHEAD("super" ".") "super" #SuperExpression(1) "." MemberSelector() + ) + | LOOKAHEAD("@" | "[" "]") {forceTypeContext();} Dims() #ArrayType(2) (MethodReference() | "." "class" #ClassLiteral(1)) + | "[" Expression() "]" #ArrayAccess(2) } /** @@ -2516,38 +2552,39 @@ void PrimarySuffix() #void : {} { MethodReference() -| ArgumentList() #MethodCall(2) -| ("[" Expression() "]") #ArrayAccess(2) +//| ArgumentList() #MethodCall(2) +| "[" Expression() "]" #ArrayAccess(2) // all the following start with a "." -| LOOKAHEAD(2) "." "this" #ThisExpression(1) -| LOOKAHEAD(2) QualifiedAllocationExpr() -| LOOKAHEAD("." [TypeArguments()] ) MemberSelector() // more complex method call patterns +//| LOOKAHEAD(2) "." "this" #ThisExpression(1) +| "." MemberSelector() } private void SuffixLAhead() #void: {} { // not "super", otherwise runs into conflicts with ExplicitConstructorInvocation - "." ("new" | "this" | [ TypeArguments() ] ) + "." ("new" | | "<") | "::" | "[" - | "(" } void MemberSelector() #void : {} { + QualifiedAllocationExpr() // if there are type arguments, this is a method call - LOOKAHEAD(2) ("." {jjtree.extendLeft();} TypeArguments() {setLastTokenImage (jjtThis) ;} ArgumentList()) #MethodCall -| LOOKAHEAD(3) ("." {jjtree.extendLeft();} {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall -| ("." {jjtree.extendLeft();} {setLastTokenImage(jjtThis);}) #FieldAccess +| (TypeArguments() {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall(3) +| LOOKAHEAD(2) ( {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall(2) +| ( {setLastTokenImage(jjtThis);}) #FieldAccess(1) } void MethodReference(): // LHS is injected {checkForBadMethodReferenceUsage();} { "::" {jjtree.extendLeft();} - [TypeArguments()] ( "new" | ) {setLastTokenImage(jjtThis);} + [TypeArguments()] + ( "new" | ) {setLastTokenImage(jjtThis);} + {/*empty*/} } void LambdaExpression(): @@ -2640,7 +2677,6 @@ void ArgumentList() : void QualifiedAllocationExpr() #ConstructorCall: {} { - "." "new" {jjtree.extendLeft();} [ TypeArguments() ] diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayType.java index 95aa66ef2e..58e249b402 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayType.java @@ -14,7 +14,7 @@ package net.sourceforge.pmd.lang.java.ast; * * */ -public final class ASTArrayType extends AbstractJavaTypeNode implements ASTReferenceType { +public final class ASTArrayType extends AbstractJavaTypeNode implements ASTReferenceType, LeftRecursiveNode { ASTArrayType(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassLiteral.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassLiteral.java index 3bffbd39ff..6f9ac466c2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassLiteral.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassLiteral.java @@ -15,7 +15,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * */ -public final class ASTClassLiteral extends AbstractJavaTypeNode implements ASTLiteral { +public final class ASTClassLiteral extends AbstractJavaTypeNode implements ASTLiteral, LeftRecursiveNode { ASTClassLiteral(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaParameter.java index 729aafd6cf..66943bca24 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaParameter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaParameter.java @@ -9,6 +9,8 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.List; + import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java index 36d53f53c4..307db8b7ec 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java @@ -35,12 +35,21 @@ public final class ASTMethodReference extends AbstractJavaTypeNode implements AS } + @Override + public void jjtClose() { + super.jjtClose(); + ASTReferenceType lhs = getLhsType(); + if (isConstructorReference() && lhs instanceof ASTAmbiguousName) { + replaceChildAt(0, ((ASTAmbiguousName) lhs).forceTypeContext()); + } + } + /** * Returns true if this is a constructor reference, * e.g. {@code ArrayList::new}. */ public boolean isConstructorReference() { - return getImage().equals("new"); + return "new".equals(getImage()); } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodReferenceTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodReferenceTest.kt index 8f1724aa4a..7af907473e 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodReferenceTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodReferenceTest.kt @@ -83,6 +83,12 @@ class ASTMethodReferenceTest : ParserTestSpec({ } } } + + "java.util.Map.Entry::foo" should matchExpr { + + it::getMethodName shouldBe "foo" + it::getLhsType shouldBe classType("Entry") // ignore the rest + } } parserTest("Constructor reference") {