Skip to content

Commit

Permalink
only validate once per primary source element
Browse files Browse the repository at this point in the history
  • Loading branch information
LorenzoBettini committed May 17, 2024
1 parent 83e6497 commit a1ae221
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,86 @@ class ActiveAnnotationsRuntimeTest extends AbstractReusableActiveAnnotationTests
]
}

@Test def void testGeneratedInferredInterfaceIsNotValidated_issue_3045() {
assertProcessing(
'extract/Extract.xtend' -> '''
package extract
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.RegisterGlobalsContext
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.Visibility
/**
* Extracts an interface for all locally declared public methods.
*/
@Target(ElementType.TYPE)
@Active(ExtractProcessor)
annotation Extract {}
class ExtractProcessor extends AbstractClassProcessor {
override doRegisterGlobals(ClassDeclaration annotatedClass, RegisterGlobalsContext context) {
context.registerInterface(annotatedClass.interfaceName)
}
def getInterfaceName(ClassDeclaration annotatedClass) {
annotatedClass.qualifiedName+"Interface"
}
override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
val interfaceType = findInterface(annotatedClass.interfaceName)
interfaceType.primarySourceElement = annotatedClass
// add the interface to the list of implemented interfaces
annotatedClass.implementedInterfaces = annotatedClass.implementedInterfaces + #[interfaceType.newTypeReference]
// add the public methods to the interface
for (method : annotatedClass.declaredMethods) {
if (method.visibility == Visibility.PUBLIC) {
interfaceType.addMethod(method.simpleName) [
docComment = method.docComment
returnType = method.returnType
for (p : method.parameters) {
addParameter(p.simpleName, p.type)
}
exceptions = method.exceptions
primarySourceElement = method
]
}
}
}
}
''',
'extract/ExtractExample.xtend' -> '''
package extract
@Extract
class ExtractExample {
/**
* This method is extracted to an interface
*/
override void myPublicMethod() {
}
/**
* This method is not extracted
*/
protected def void myPrivateMethod() {
}
}
'''
)[
validator.assertNoErrors(xtendFile)
]
}
}

class DelegatingClassloader extends ClassLoader {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,4 +436,189 @@ public void testDetectOrphanedElements2() {
};
this.assertProcessing(_mappedTo, _mappedTo_1, _function);
}

@Test
public void testGeneratedInferredInterfaceIsNotValidated_issue_3045() {
StringConcatenation _builder = new StringConcatenation();
_builder.append("package extract");
_builder.newLine();
_builder.newLine();
_builder.append("import java.lang.annotation.ElementType");
_builder.newLine();
_builder.append("import java.lang.annotation.Target");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.AbstractClassProcessor");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.Active");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.RegisterGlobalsContext");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.TransformationContext");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration");
_builder.newLine();
_builder.append("import org.eclipse.xtend.lib.macro.declaration.Visibility");
_builder.newLine();
_builder.newLine();
_builder.append("/**");
_builder.newLine();
_builder.append(" ");
_builder.append("* Extracts an interface for all locally declared public methods.");
_builder.newLine();
_builder.append(" ");
_builder.append("*/");
_builder.newLine();
_builder.append("@Target(ElementType.TYPE)");
_builder.newLine();
_builder.append("@Active(ExtractProcessor)");
_builder.newLine();
_builder.append("annotation Extract {}");
_builder.newLine();
_builder.newLine();
_builder.append("class ExtractProcessor extends AbstractClassProcessor {");
_builder.newLine();
_builder.append("\t");
_builder.newLine();
_builder.append("\t");
_builder.append("override doRegisterGlobals(ClassDeclaration annotatedClass, RegisterGlobalsContext context) {");
_builder.newLine();
_builder.append("\t\t");
_builder.append("context.registerInterface(annotatedClass.interfaceName)");
_builder.newLine();
_builder.append("\t");
_builder.append("}");
_builder.newLine();
_builder.newLine();
_builder.append("\t");
_builder.append("def getInterfaceName(ClassDeclaration annotatedClass) {");
_builder.newLine();
_builder.append("\t\t");
_builder.append("annotatedClass.qualifiedName+\"Interface\"");
_builder.newLine();
_builder.append("\t");
_builder.append("}");
_builder.newLine();
_builder.append("\t");
_builder.newLine();
_builder.append("\t");
_builder.append("override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {");
_builder.newLine();
_builder.append("\t\t");
_builder.append("val interfaceType = findInterface(annotatedClass.interfaceName)");
_builder.newLine();
_builder.append("\t\t");
_builder.append("interfaceType.primarySourceElement = annotatedClass");
_builder.newLine();
_builder.append("\t\t");
_builder.append("// add the interface to the list of implemented interfaces");
_builder.newLine();
_builder.append("\t\t");
_builder.append("annotatedClass.implementedInterfaces = annotatedClass.implementedInterfaces + #[interfaceType.newTypeReference]");
_builder.newLine();
_builder.append("\t\t");
_builder.newLine();
_builder.append("\t\t");
_builder.append("// add the public methods to the interface");
_builder.newLine();
_builder.append("\t\t");
_builder.append("for (method : annotatedClass.declaredMethods) {");
_builder.newLine();
_builder.append("\t\t\t");
_builder.append("if (method.visibility == Visibility.PUBLIC) {");
_builder.newLine();
_builder.append("\t\t\t\t");
_builder.append("interfaceType.addMethod(method.simpleName) [");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("docComment = method.docComment");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("returnType = method.returnType");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("for (p : method.parameters) {");
_builder.newLine();
_builder.append("\t\t\t\t\t\t");
_builder.append("addParameter(p.simpleName, p.type)");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("}");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("exceptions = method.exceptions");
_builder.newLine();
_builder.append("\t\t\t\t\t");
_builder.append("primarySourceElement = method");
_builder.newLine();
_builder.append("\t\t\t\t");
_builder.append("]");
_builder.newLine();
_builder.append("\t\t\t");
_builder.append("}");
_builder.newLine();
_builder.append("\t\t");
_builder.append("}");
_builder.newLine();
_builder.append("\t");
_builder.append("}");
_builder.newLine();
_builder.append("\t");
_builder.newLine();
_builder.append("}");
_builder.newLine();
Pair<String, String> _mappedTo = Pair.<String, String>of("extract/Extract.xtend", _builder.toString());
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("package extract");
_builder_1.newLine();
_builder_1.newLine();
_builder_1.append("@Extract");
_builder_1.newLine();
_builder_1.append("class ExtractExample {");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("/**");
_builder_1.newLine();
_builder_1.append("\t ");
_builder_1.append("* This method is extracted to an interface");
_builder_1.newLine();
_builder_1.append("\t ");
_builder_1.append("*/");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("override void myPublicMethod() {");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("}");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("/**");
_builder_1.newLine();
_builder_1.append("\t ");
_builder_1.append("* This method is not extracted");
_builder_1.newLine();
_builder_1.append("\t ");
_builder_1.append("*/");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("protected def void myPrivateMethod() {");
_builder_1.newLine();
_builder_1.append("\t\t");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("}");
_builder_1.newLine();
_builder_1.append("}");
_builder_1.newLine();
Pair<String, String> _mappedTo_1 = Pair.<String, String>of("extract/ExtractExample.xtend", _builder_1.toString());
final Procedure1<CompilationUnitImpl> _function = (CompilationUnitImpl it) -> {
this.validator.assertNoErrors(it.getXtendFile());
};
this.assertProcessing(_mappedTo, _mappedTo_1, _function);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ public void interceptRoot(EObject o) {
}

protected void checkJvmGenericTypes(List<? extends EObject> contents) {
var processed = new HashSet<EObject>();
contents.stream()
.filter(JvmGenericType.class::isInstance)
.map(JvmGenericType.class::cast)
.filter(this::shouldBeValidated)
.forEach(this::checkJvmGenericType);
.forEach(t -> checkJvmGenericType(t, processed));
}

/**
Expand All @@ -171,8 +172,12 @@ protected boolean shouldBeValidated(JvmIdentifiableElement element) {
/**
* The method assumes that the passed {@link JvmGenericType} has an associated source.
*/
protected void checkJvmGenericType(JvmGenericType type) {
protected void checkJvmGenericType(JvmGenericType type, Set<EObject> processed) {
var sourceType = getPrimarySourceElement(type);
// inferred types must be checked once per primary source element
// see https://github.com/eclipse/xtext/issues/3045
if (!processed.add(sourceType))
return;
handleExceptionDuringValidation(() -> checkDefaultSuperConstructor(sourceType, type));
handleExceptionDuringValidation(() -> checkSuperTypes(sourceType, type));
IterableExtensions.toList(type.getDeclaredFields()).stream()
Expand All @@ -188,7 +193,7 @@ protected void checkJvmGenericType(JvmGenericType type) {
EcoreUtil2.eAllOfType(member, JvmGenericType.class).stream()
.filter(this::shouldBeValidated)
.forEach(nestedType ->
handleExceptionDuringValidation(() -> checkJvmGenericType(nestedType)));
handleExceptionDuringValidation(() -> checkJvmGenericType(nestedType, processed)));
});
}

Expand Down

0 comments on commit a1ae221

Please sign in to comment.