You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/10/01 17:25:29 UTC
[groovy] branch master updated: GROOVY-10770: STC: extension composition
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 168ac8f269 GROOVY-10770: STC: extension composition
168ac8f269 is described below
commit 168ac8f269620f3d2bef85dbabd364823f8f2dec
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Oct 1 11:46:52 2022 -0500
GROOVY-10770: STC: extension composition
---
build.gradle | 3 +-
.../org/codehaus/groovy/ast/AnnotatedNode.java | 6 +-
.../stc/AbstractTypeCheckingExtension.java | 39 +++---
.../stc/DefaultTypeCheckingExtension.java | 20 +--
.../stc/GroovyTypeCheckingExtensionSupport.java | 37 +++--
.../transform/stc/StaticTypeCheckingVisitor.java | 74 ++++++++--
src/test/groovy/bugs/Groovy10770.groovy | 152 +++++++++++++++++++++
7 files changed, 275 insertions(+), 56 deletions(-)
diff --git a/build.gradle b/build.gradle
index 85643bd41c..6480853a76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -68,7 +68,8 @@ groovyCore {
bridgedClasses 'org.codehaus.groovy.runtime.DefaultGroovyMethods',
'org.codehaus.groovy.runtime.StringGroovyMethods',
'org.codehaus.groovy.classgen.Verifier',
- 'org.codehaus.groovy.ast.tools.GeneralUtils'
+ 'org.codehaus.groovy.ast.tools.GeneralUtils',
+ 'org.codehaus.groovy.transform.stc.DefaultTypeCheckingExtension'
excludeFromJavadoc '**/GroovyRecognizer.java', '*.g4' // generated file
}
diff --git a/src/main/java/org/codehaus/groovy/ast/AnnotatedNode.java b/src/main/java/org/codehaus/groovy/ast/AnnotatedNode.java
index ed3d4bdc5c..2077185edf 100644
--- a/src/main/java/org/codehaus/groovy/ast/AnnotatedNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/AnnotatedNode.java
@@ -38,13 +38,13 @@ public class AnnotatedNode extends ASTNode implements GroovydocHolder<AnnotatedN
}
public List<AnnotationNode> getAnnotations(final ClassNode type) {
- List<AnnotationNode> ret = new ArrayList<>(annotations.size());
+ List<AnnotationNode> annotations = new ArrayList<>();
for (AnnotationNode node : getAnnotations()) {
if (type.equals(node.getClassNode())) {
- ret.add(node);
+ annotations.add(node);
}
}
- return ret;
+ return annotations;
}
public AnnotationNode addAnnotation(final ClassNode type) {
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/AbstractTypeCheckingExtension.java b/src/main/java/org/codehaus/groovy/transform/stc/AbstractTypeCheckingExtension.java
index b0d4af6f65..abcfb6ed22 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/AbstractTypeCheckingExtension.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/AbstractTypeCheckingExtension.java
@@ -19,6 +19,9 @@
package org.codehaus.groovy.transform.stc;
import groovy.lang.Closure;
+import groovy.lang.DelegatesTo;
+import groovy.transform.stc.ClosureParams;
+import groovy.transform.stc.SimpleType;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.ClassHelper;
@@ -36,6 +39,7 @@ import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.classgen.asm.InvocationWriter;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.objectweb.asm.Opcodes;
import java.util.Collections;
@@ -45,7 +49,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
-import java.util.logging.Logger;
/**
* <p>Custom type checking extensions may extend this method in order to benefit from a lot
@@ -57,19 +60,20 @@ import java.util.logging.Logger;
* @since 2.3.0
*/
public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
- private static final Logger LOG = Logger.getLogger(GroovyTypeCheckingExtensionSupport.class.getName());
+
protected final TypeCheckingContext context;
+ /** @see {@link #log(String)} */ protected boolean debug;
+ /** @see {@link #setHandled(boolean)} */ protected boolean handled;
private final Set<MethodNode> generatedMethods = new LinkedHashSet<>();
private final LinkedList<TypeCheckingScope> scopeData = new LinkedList<>();
- // this boolean is used through setHandled(boolean)
- protected boolean handled = false;
- protected boolean debug = false;
public AbstractTypeCheckingExtension(final StaticTypeCheckingVisitor typeCheckingVisitor) {
super(typeCheckingVisitor);
this.context = typeCheckingVisitor.typeCheckingContext;
}
+ //--------------------------------------------------------------------------
+
public void setHandled(final boolean handled) {
this.handled = handled;
}
@@ -246,12 +250,9 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
return argTypeMatches(argumentTypes, index, clazz);
}
- @SuppressWarnings("unchecked")
- public <R> R withTypeChecker(Closure<R> code) {
- Closure<R> clone = (Closure<R>) code.clone();
- clone.setDelegate(typeCheckingVisitor);
- clone.setResolveStrategy(Closure.DELEGATE_FIRST);
- return clone.call();
+ public <R> R withTypeChecker(@DelegatesTo(value = StaticTypeCheckingVisitor.class, strategy = Closure.DELEGATE_FIRST)
+ @ClosureParams(value = SimpleType.class, options = "org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor") final Closure<R> code) {
+ return DefaultGroovyMethods.with(typeCheckingVisitor, code);
}
/**
@@ -283,7 +284,7 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
}
setHandled(true);
if (debug) {
- LOG.info("Turning " + call.getText() + " into a dynamic method call returning " + StaticTypeCheckingSupport.prettyPrintType(returnType));
+ log("Turning " + call.getText() + " into a dynamic method call returning " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
return new MethodNode(call.getMethodAsString(), 0, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
}
@@ -309,7 +310,7 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
storeType(pexp, returnType);
setHandled(true);
if (debug) {
- LOG.info("Turning '" + pexp.getText() + "' into a dynamic property access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
+ log("Turning '" + pexp.getText() + "' into a dynamic property access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
}
@@ -334,12 +335,13 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
storeType(vexp, returnType);
setHandled(true);
if (debug) {
- LOG.info("Turning '" + vexp.getText() + "' into a dynamic variable access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
+ log("Turning '" + vexp.getText() + "' into a dynamic variable access of type " + StaticTypeCheckingSupport.prettyPrintType(returnType));
}
}
- public void log(String message) {
- LOG.info(message);
+ public void log(final String message) {
+ java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GroovyTypeCheckingExtensionSupport.class.getName());
+ logger.info(message);
}
public BinaryExpression getEnclosingBinaryExpression() {
@@ -434,7 +436,9 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
context.pushTemporaryTypeInfo();
}
- private static class TypeCheckingScope extends LinkedHashMap<String, Object> {
+ //--------------------------------------------------------------------------
+
+ public static class TypeCheckingScope extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 7607331333917615144L;
private final AbstractTypeCheckingExtension.TypeCheckingScope parent;
@@ -445,6 +449,5 @@ public class AbstractTypeCheckingExtension extends TypeCheckingExtension {
public AbstractTypeCheckingExtension.TypeCheckingScope getParent() {
return parent;
}
-
}
}
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/DefaultTypeCheckingExtension.java b/src/main/java/org/codehaus/groovy/transform/stc/DefaultTypeCheckingExtension.java
index 4cdaf1f40c..f5a7426c91 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/DefaultTypeCheckingExtension.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/DefaultTypeCheckingExtension.java
@@ -29,7 +29,6 @@ import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
-import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -47,17 +46,23 @@ import java.util.List;
* @since 2.1.0
*/
public class DefaultTypeCheckingExtension extends TypeCheckingExtension {
+
protected final List<TypeCheckingExtension> handlers = new LinkedList<TypeCheckingExtension>();
public DefaultTypeCheckingExtension(final StaticTypeCheckingVisitor typeCheckingVisitor) {
super(typeCheckingVisitor);
}
- public void addHandler(TypeCheckingExtension handler) {
+ @Deprecated
+ public void addHandler$$bridge(final TypeCheckingExtension handler) {
handlers.add(handler);
}
- public void removeHandler(TypeCheckingExtension handler) {
+ public boolean addHandler(final TypeCheckingExtension handler) {
+ return !handlers.contains(handler) ? handlers.add(handler) : false;
+ }
+
+ public void removeHandler(final TypeCheckingExtension handler) {
handlers.remove(handler);
}
@@ -181,17 +186,16 @@ public class DefaultTypeCheckingExtension extends TypeCheckingExtension {
@Override
public void setup() {
- ArrayList<TypeCheckingExtension> copy = new ArrayList<TypeCheckingExtension>(handlers);
- // we're using a copy here because new extensions can be added during the "setup" phase
- for (TypeCheckingExtension handler : copy) {
+ // We're using a copy here because new extensions can be added during the setup phase!
+ for (TypeCheckingExtension handler : handlers.toArray(new TypeCheckingExtension[0])) {
handler.setup();
}
}
@Override
public void finish() {
- for (TypeCheckingExtension handler : handlers) {
+ for (TypeCheckingExtension handler : handlers.toArray(new TypeCheckingExtension[0])) {
handler.finish();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
index 5a72984917..eb31312bfa 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java
@@ -52,6 +52,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Base class for type checking extensions written in Groovy. Compared to its superclass, {@link TypeCheckingExtension},
@@ -86,6 +87,8 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
private final CompilationUnit compilationUnit;
+ private TypeCheckingExtension delegateExtension;
+
/** Closures executed in event-based methods. */
private final Map<String, List<Closure>> eventHandlers = new HashMap<>();
@@ -104,6 +107,21 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
this.compilationUnit = compilationUnit;
}
+ @Override
+ public boolean equals(Object that) {
+ if (that == this) return true;
+ if (that == null || that.getClass() != this.getClass()) return false;
+ GroovyTypeCheckingExtensionSupport support = (GroovyTypeCheckingExtensionSupport) that;
+ return Objects.equals(scriptPath,support.scriptPath) && Objects.equals(compilationUnit,support.compilationUnit);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(scriptPath, compilationUnit);
+ }
+
+ //--------------------------------------------------------------------------
+
public void setDebug(final boolean debug) {
this.debug = debug;
}
@@ -133,9 +151,9 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
// since 2.4, we can also register precompiled type checking extensions which are not scripts
try {
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(StaticTypeCheckingVisitor.class);
- TypeCheckingExtension extension = (TypeCheckingExtension) declaredConstructor.newInstance(typeCheckingVisitor);
- typeCheckingVisitor.addTypeCheckingExtension(extension);
- extension.setup();
+ delegateExtension = (TypeCheckingExtension) declaredConstructor.newInstance(typeCheckingVisitor);
+ typeCheckingVisitor.addTypeCheckingExtension(delegateExtension);
+ delegateExtension.setup();
return;
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
addLoadingError(config);
@@ -202,6 +220,11 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
@Override
public void finish() {
+ if (delegateExtension != null) { typeCheckingVisitor.extension.removeHandler(delegateExtension);
+ delegateExtension.finish();
+ return;
+ }
+
List<Closure> list = eventHandlers.get("finish");
if (list != null) {
for (Closure closure : list) {
@@ -392,14 +415,6 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte
return methodList;
}
- // -------------------------------------
- // delegate to the type checking context
- // -------------------------------------
-
- // --------------------------------------------
- // end of delegate to the type checking context
- // --------------------------------------------
-
public abstract static class TypeCheckingDSL extends Script {
private GroovyTypeCheckingExtensionSupport extension;
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index cfe5ab5315..b60c50f5f4 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -138,6 +138,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -395,6 +396,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
this.extension.addHandler(new TraitTypeCheckingExtension(this));
}
+ public void setCompilationUnit(final CompilationUnit compilationUnit) {
+ typeCheckingContext.setCompilationUnit(compilationUnit);
+ }
+
@Override
protected SourceUnit getSourceUnit() {
return typeCheckingContext.getSource();
@@ -404,6 +409,17 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
extension.setup();
}
+ /**
+ * Returns array of type checking annotations. Subclasses may override this
+ * method in order to provide additional types which must be looked up when
+ * checking if a method or a class node should be skipped.
+ * <p>
+ * The default implementation returns {@link TypeChecked}.
+ */
+ protected ClassNode[] getTypeCheckingAnnotations() {
+ return TYPECHECKING_ANNOTATIONS;
+ }
+
/**
* Returns the current type checking context. The context is used internally by the type
* checker during type checking to store various state data.
@@ -418,10 +434,49 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
this.extension.addHandler(extension);
}
- public void setCompilationUnit(final CompilationUnit compilationUnit) {
- typeCheckingContext.setCompilationUnit(compilationUnit);
+ private List<TypeCheckingExtension> getTypeCheckingExtensions(final AnnotatedNode classOrMethod) {
+ List<Expression> ex = new ArrayList<>();
+ for (ClassNode type : getTypeCheckingAnnotations()) {
+ for (AnnotationNode anno : classOrMethod.getAnnotations(type)) {
+ Expression extensions = anno.getMember("extensions");
+ if (extensions instanceof ConstantExpression) {
+ ex.add(extensions);
+ } else if (extensions instanceof ListExpression) {
+ ex.addAll(((ListExpression) extensions).getExpressions());
+ }
+ }
+ }
+ if (ex.isEmpty()) return Collections.emptyList();
+ return ex.stream().filter(e -> e instanceof ConstantExpression).map(pathOrType ->
+ new GroovyTypeCheckingExtensionSupport(this, pathOrType.getText(), typeCheckingContext.getCompilationUnit())
+ ).collect(Collectors.toList());
+ }
+
+ private <Node extends AnnotatedNode> void doWithTypeCheckingExtensions(final Node classOrMethod, final Consumer<Node> visitor) {
+ List<TypeCheckingExtension> extensions = getTypeCheckingExtensions(classOrMethod);
+ if (extensions.isEmpty()) {
+ visitor.accept(classOrMethod);
+ } else { // GROOVY-10770: extension composition
+ List<TypeCheckingExtension> added = new ArrayList<>();
+ for (TypeCheckingExtension ext : extensions) {
+ if (extension.addHandler(ext)) {
+ added.add(ext);
+ ext.setup();
+ }
+ }
+ try {
+ visitor.accept(classOrMethod);
+ } finally {
+ for (TypeCheckingExtension ext : added) {
+ extension.removeHandler(ext);
+ ext.finish();
+ }
+ }
+ }
}
+ //--------------------------------------------------------------------------
+
@Override
public void visitClass(final ClassNode node) {
if (shouldSkipClassNode(node)) return;
@@ -436,7 +491,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
Set<MethodNode> oldSet = typeCheckingContext.alreadyVisitedMethods;
typeCheckingContext.alreadyVisitedMethods = new LinkedHashSet<>();
- super.visitClass(node);
+ doWithTypeCheckingExtensions(node, super::visitClass);
node.getInnerClasses().forEachRemaining(this::visitClass);
typeCheckingContext.alreadyVisitedMethods = oldSet;
@@ -455,17 +510,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
extension.afterVisitClass(node);
}
- /**
- * Returns array of type checking annotations. Subclasses may override this
- * method in order to provide additional types which must be looked up when
- * checking if a method or a class node should be skipped.
- * <p>
- * The default implementation returns {@link TypeChecked}.
- */
- protected ClassNode[] getTypeCheckingAnnotations() {
- return TYPECHECKING_ANNOTATIONS;
- }
-
protected boolean shouldSkipClassNode(final ClassNode node) {
return Boolean.TRUE.equals(node.getNodeMetaData(StaticTypeCheckingVisitor.class)) || isSkipMode(node);
}
@@ -2595,7 +2639,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
new ReturnAdder(returnStmt -> applyTargetType(returnType, returnStmt.getExpression())).visitMethod(node);
}
readClosureParameterAnnotation(node); // GROOVY-6603
- super.visitConstructorOrMethod(node, isConstructor);
+ doWithTypeCheckingExtensions(node, it -> super.visitConstructorOrMethod(it, isConstructor));
if (node.hasDefaultValue()) {
visitDefaultParameterArguments(node.getParameters());
}
diff --git a/src/test/groovy/bugs/Groovy10770.groovy b/src/test/groovy/bugs/Groovy10770.groovy
new file mode 100644
index 0000000000..cfbba430ad
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy10770.groovy
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.bugs
+
+import groovy.test.NotYetImplemented
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+final class Groovy10770 {
+
+ private final GroovyShell shell = GroovyShell.withConfig {
+ imports {
+ star 'groovy.transform'
+ }
+ }
+
+ @Test
+ void testCheckError1() {
+ def err = shouldFail shell, '''
+ @TypeChecked
+ class C {
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ '''
+ assert err =~ /The variable .bar. is undeclared/
+ }
+
+ @Test
+ void testCheckError2() {
+ def err = shouldFail shell, '''
+ @TypeChecked(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ '''
+ assert err =~ /No such property: baz for class: java.lang.String/
+ }
+
+ @Test
+ void testExtensions1() {
+ assertScript shell, '''
+ @TypeChecked(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ @TypeChecked(extensions='groovy/transform/stc/UnresolvedPropertyTestExtension.groovy')
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ def c = new C()
+ def d = new C.D(c)
+ '''
+ }
+
+ @Test
+ void testExtensions2() {
+ def err = shouldFail shell, '''
+ @CompileStatic(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ @CompileStatic(extensions='groovy/transform/stc/UnresolvedPropertyTestExtension.groovy')
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ def c = new C()
+ def d = new C.D(c)
+ '''
+ assert err =~ /Access to java.lang.String#baz is forbidden/ // passes STC but fails during compilation
+ }
+
+ @Test
+ void testExtensions3() {
+ def err = shouldFail shell, '''
+ @CompileStatic(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ @TypeChecked(extensions='groovy/transform/stc/UnresolvedPropertyTestExtension.groovy')
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ def c = new C()
+ def d = new C.D(c)
+ '''
+ assert err =~ /Access to java.lang.String#baz is forbidden/ // passes STC but fails during compilation
+ }
+
+ @Test @NotYetImplemented
+ void testExtensions4() {
+ def err = shouldFail shell, '''
+ @TypeChecked(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ @CompileStatic(extensions='groovy/transform/stc/UnresolvedPropertyTestExtension.groovy')
+ class D {
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ def c = new C()
+ def d = new C.D(c)
+ '''
+ assert err =~ /Access to java.lang.String#baz is forbidden/ // passes STC but fails during compilation
+ }
+
+ @Test // annotate method
+ void testExtensions5() {
+ assertScript shell, '''
+ @TypeChecked(extensions='groovy/transform/stc/UndefinedVariableTestExtension.groovy')
+ class C {
+ class D {
+ @TypeChecked(extensions='groovy/transform/stc/UnresolvedPropertyTestExtension.groovy')
+ def foo() {
+ bar.baz
+ }
+ }
+ }
+ def c = new C()
+ def d = new C.D(c)
+ '''
+ }
+}