Skip to content

Commit

Permalink
Fix type detection of nested annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
facboy committed Feb 12, 2024
1 parent 8bea251 commit 2acce8b
Show file tree
Hide file tree
Showing 30 changed files with 866 additions and 27 deletions.
6 changes: 3 additions & 3 deletions buildScripts/vm-finder.ant.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ and rerun the build; this build is capable of finding VMs automatically on many
<matches pattern="^\s*$" string="${jvm.loc}" />
</condition>
<fail if="jvm.loc.aborted">aborted</fail>
<condition property="jvm.loc.invalid">
<not><available file="${jvm.loc}/bin/${exe.java}" type="file" /></not>
</condition>
<fail if="jvm.loc.invalid">.

ERROR: That does not appear to be a valid location; ${jvm.loc}/bin/${exe.java} should exist.
<condition>
<not><available file="${jvm.loc}/bin/${exe.java}" type="file" /></not>
</condition>
</fail>
<exec executable="${jvm.loc}/bin/${exe.java}" errorproperty="jvm.versioncheck.answer" failifexecutionfails="false" resultproperty="jvm.versioncheck.result">
<arg value="-version" />
Expand Down
139 changes: 115 additions & 24 deletions src/core/lombok/core/TypeResolver.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/*
* Copyright (C) 2009-2020 The Project Lombok Authors.
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand All @@ -21,6 +21,8 @@
*/
package lombok.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import lombok.core.AST.Kind;
Expand All @@ -32,27 +34,35 @@
* and this importer also can't find inner types from superclasses/interfaces.
*/
public class TypeResolver {
private ImportList imports;
private final ImportList imports;

/**
* Creates a new TypeResolver that can be used to resolve types in a source file with the given package and import statements.
*/
public TypeResolver(ImportList importList) {
this.imports = importList;
}

public boolean typeMatches(LombokNode<?, ?, ?> context, String fqn, String typeRef) {
return typeRefToFullyQualifiedName(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef) != null;
}

public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) {
// When asking if 'Foo' could possibly be referring to 'bar.Baz', the answer is obviously no.
List<String> qualifieds = library.toQualifieds(typeRef);
if (qualifieds == null || qualifieds.isEmpty()) return null;

// When asking if 'lombok.Getter' could possibly be referring to 'lombok.Getter', the answer is obviously yes.
if (qualifieds.contains(typeRef)) return LombokInternalAliasing.processAliases(typeRef);


// Types defined on the containing type, or on any of its parents in the source file, take precedence over any imports
String nestedTypeFqn = new NestedTypeFinder(typeRef).findNestedType(context);
if (nestedTypeFqn != null) {
// we found a nestedType - check edge case where nestedType is in type library
qualifieds = library.toQualifieds(nestedTypeFqn);
return qualifieds == null || !qualifieds.contains(nestedTypeFqn) ? null : nestedTypeFqn;
}

// When asking if 'Getter' could possibly be referring to 'lombok.Getter' if 'import lombok.Getter;' is in the source file, the answer is yes.
int firstDot = typeRef.indexOf('.');
if (firstDot == -1) firstDot = typeRef.length();
Expand All @@ -64,26 +74,26 @@ public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibra
// ... and if 'import foobar.Getter;' is in the source file, the answer is no.
return null;
}

// When asking if 'Getter' could possibly be referring to 'lombok.Getter' and 'import lombok.*; / package lombok;' isn't in the source file. the answer is no.
for (String qualified : qualifieds) {
String pkgName = qualified.substring(0, qualified.length() - typeRef.length() - 1);
if (!imports.hasStarImport(pkgName)) continue;

// Now the hard part: Given that there is a star import, 'Getter' most likely refers to 'lombok.Getter', but type shadowing may occur in which case it doesn't.
LombokNode<?, ?, ?> n = context;

mainLoop:
while (n != null) {
if (n.getKind() == Kind.TYPE && firstTypeRef.equals(n.getName())) {
// Our own class or one of our outer classes is named 'typeRef' so that's what 'typeRef' is referring to, not one of our type library classes.
return null;
}

if (n.getKind() == Kind.STATEMENT || n.getKind() == Kind.LOCAL) {
LombokNode<?, ?, ?> newN = n.directUp();
if (newN == null) break mainLoop;

if (newN.getKind() == Kind.STATEMENT || newN.getKind() == Kind.INITIALIZER || newN.getKind() == Kind.METHOD) {
for (LombokNode<?, ?, ?> child : newN.down()) {
// We found a method local with the same name above our code. That's the one 'typeRef' is referring to, not
Expand All @@ -95,22 +105,103 @@ public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibra
n = newN;
continue mainLoop;
}

if (n.getKind() == Kind.TYPE || n.getKind() == Kind.COMPILATION_UNIT) {
for (LombokNode<?, ?, ?> child : n.down()) {
// Inner class that's visible to us has 'typeRef' as name, so that's the one being referred to, not one of our type library classes.
if (child.getKind() == Kind.TYPE && firstTypeRef.equals(child.getName())) return null;
}
}


// don't need to check for inner class shadowing, we already do that in NestedTypeFinder

n = n.directUp();
}

// If no shadowing thing has been found, the star import 'wins', so, return that.
return LombokInternalAliasing.processAliases(qualified);
}

// No star import matches either.
return null;
}

/**
* Traverse up the containing types until we find a match, or hit the package. At each level,
* we check for a type with matching name (including traversing into child types if typeRef is
* not a simple name).
*/
private static class NestedTypeFinder {

private final String typeRef;
private final List<String> typeRefElements;

public NestedTypeFinder(String typeRef) {
this.typeRef = typeRef;
this.typeRefElements = Arrays.asList(typeRef.split("\\.", -1));
}

/** Finds a matching nestedType and returns its FQN, or {@code null} if no match found. */
public String findNestedType(LombokNode<?, ?, ?> context) {
LombokNode<?, ?, ?> nearestType = traverseUpToNearestType(context);
if (nearestType == null) {
return null;
}

boolean found = findTypeRef(nearestType, 0);
if (found) {
// return FQN
return getFoundFqn(nearestType);
}

return findNestedType(nearestType.up());
}

/** Traverse up to the nearest type or package (including {@code node} if it is a type). */
private LombokNode<?, ?, ?> traverseUpToNearestType(LombokNode<?, ?, ?> node) {
if (node == null) {
return null; // parent is null once we hit the package
}
if (node.getKind() == Kind.COMPILATION_UNIT || node.getKind() == Kind.TYPE) {
return node;
}
return traverseUpToNearestType(node.up());
}

/** Check whether {@code typeRef[nameIndex]} exists as a child of {@code typeNode}. */
private boolean findTypeRef(LombokNode<?, ?, ?> typeNode, int nameIndex) {
for (LombokNode<?, ?, ?> child : typeNode.down()) {
if (child.getKind() == Kind.TYPE) {
// check if this node matches the first element
if (child.getName().equals(typeRefElements.get(nameIndex))) {
if (nameIndex == typeRefElements.size() - 1) {
// we've found a match as we've matched all elements of typeRef
return true;
}
// otherwise, check match of remaining typeRef elements
boolean found = findTypeRef(child, nameIndex + 1);
if (found) {
return true;
}
}
}
}
return false;
}

private String getFoundFqn(LombokNode<?, ?, ?> typeNode) {
List<String> elements = new ArrayList<String>();
while (typeNode.getKind() != Kind.COMPILATION_UNIT) {
elements.add(typeNode.getName());
typeNode = traverseUpToNearestType(typeNode.up());
}

String pkg = typeNode.getPackageDeclaration();
StringBuilder fqn;
if (pkg == null) { // pkg can be null e.g. if top-level type is in default package
fqn = new StringBuilder(elements.size() * 10);
} else {
fqn = new StringBuilder(pkg.length() + elements.size() * 10);
fqn.append(pkg).append('.');
}
for (int i = elements.size() - 1; i >= 0; i--) {
fqn.append(elements.get(i)).append('.');
}
fqn.append(typeRef);
return fqn.toString();
}
}
}
11 changes: 11 additions & 0 deletions test/stubs/test/lombok/other/Other.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package test.lombok.other;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class Other {

@Retention(RetentionPolicy.RUNTIME)
public @interface TA {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package test.lombok;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import test.lombok.NestedClassAndAnnotationNestedInImportedSibling.Other.OtherNested.TA;

public class NestedClassAndAnnotationNestedInImportedSibling {

public static class Inner {

@TA
private final int someVal;

@java.lang.SuppressWarnings("all")
public Inner(@TA final int someVal) {
this.someVal = someVal;
}

@TA
@java.lang.SuppressWarnings("all")
public int getSomeVal() {
return this.someVal;
}
}

public static class Other {

public static class OtherNested {

@Retention(RetentionPolicy.RUNTIME)
public @interface TA {
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package test.lombok;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class NestedClassAndAnnotationNestedInNonImportedSibling {

public static class Inner {
@Other.OtherNested.TA
private final int someVal;

@java.lang.SuppressWarnings("all")
public Inner(@Other.OtherNested.TA final int someVal) {
this.someVal = someVal;
}

@Other.OtherNested.TA
@java.lang.SuppressWarnings("all")
public int getSomeVal() {
return this.someVal;
}
}

public static class Other {

public static class OtherNested {
@Retention(RetentionPolicy.RUNTIME)
public @interface TA {
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package test.lombok;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class NestedClassAndNestedAnnotation {

public static class Inner {
@TA
private final int someVal;

@java.lang.SuppressWarnings("all")
public Inner(@TA final int someVal) {
this.someVal = someVal;
}

@TA
@java.lang.SuppressWarnings("all")
public int getSomeVal() {
return this.someVal;
}
}

@Retention(RetentionPolicy.RUNTIME)
public @interface TA {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package test.lombok;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import test.lombok.NestedClassAndNestedAnnotationImported.Other.OtherNested.TA;

public class NestedClassAndNestedAnnotationImported {

public static class Inner {
@TA
private final int someVal;

@java.lang.SuppressWarnings("all")
public Inner(@TA final int someVal) {
this.someVal = someVal;
}

@TA
@java.lang.SuppressWarnings("all")
public int getSomeVal() {
return this.someVal;
}
}

public static class Other {

public static class OtherNested {
@Retention(RetentionPolicy.RUNTIME)
public @interface TA {
}
}
}
}
Loading

0 comments on commit 2acce8b

Please sign in to comment.