Skip to content
Permalink
Browse files
GROOVY-9866, GROOVY-10466: resolve all class headers before class bodies
  • Loading branch information
eric-milles committed Mar 8, 2022
1 parent 27885a7 commit 4cbdc0f329f9b357da74067b7ad1d9d332aa2552
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 38 deletions.
@@ -199,9 +199,25 @@ private void addPhaseOperations() {
}, Phases.CONVERSION);

addPhaseOperation(source -> {
resolveVisitor.setClassNodeResolver(classNodeResolver);
for (ClassNode classNode : source.getAST().getClasses()) {
resolveVisitor.startResolving(classNode, source);
try {
resolveVisitor.phase = 1; // resolve head of each class
resolveVisitor.setClassNodeResolver(classNodeResolver);
for (ClassNode classNode : source.getAST().getClasses()) {
resolveVisitor.startResolving(classNode, source);
}
} finally {
resolveVisitor.phase = 0;
}
}, Phases.SEMANTIC_ANALYSIS);

addPhaseOperation(source -> {
try {
resolveVisitor.phase = 2; // resolve body of each class
for (ClassNode classNode : source.getAST().getClasses()) {
resolveVisitor.startResolving(classNode, source);
}
} finally {
resolveVisitor.phase = 0;
}
}, Phases.SEMANTIC_ANALYSIS);

@@ -113,6 +113,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer {
private boolean checkingVariableTypeInDeclaration;
private boolean inClosure, inPropertyExpression;
private boolean isTopLevelProperty = true;
/*package*/ int phase; // sub-divide visit

/**
* A ConstructedNestedClass consists of an outer class and a name part, denoting a
@@ -423,7 +424,6 @@ protected boolean resolveNestedClass(final ClassNode type) {
// GROOVY-4043: for type "X", try "A$X" with each type in the class hierarchy (except for Object)
for (; cn != null && cycleCheck.add(cn) && !isObjectType(cn); cn = cn.getSuperClass()) {
if (setRedirect(type, cn)) return true;
// GROOVY-9866: unresolvable interfaces
}

// Another case we want to check here is if we are in a
@@ -1316,46 +1316,50 @@ public void visitClass(final ClassNode node) {
genericParameterNames = new HashMap<>();
}
resolveGenericsHeader(node.getGenericsTypes());
switch (phase) { // GROOVY-9866, GROOVY-10466
case 0:
case 1:
ClassNode sn = node.getUnresolvedSuperClass();
if (sn != null) {
resolveOrFail(sn, "", node, true);
}
for (ClassNode in : node.getInterfaces()) {
resolveOrFail(in, "", node, true);
}

ClassNode sn = node.getUnresolvedSuperClass();
if (sn != null) {
resolveOrFail(sn, "", node, true);
}
for (ClassNode in : node.getInterfaces()) {
resolveOrFail(in, "", node, true);
}

if (sn != null) checkCyclicInheritance(node, sn);
for (ClassNode in : node.getInterfaces()) {
checkCyclicInheritance(node, in);
}
if (node.getGenericsTypes() != null) {
for (GenericsType gt : node.getGenericsTypes()) {
if (gt != null && gt.getUpperBounds() != null) {
for (ClassNode variant : gt.getUpperBounds()) {
if (variant.isGenericsPlaceHolder()) checkCyclicInheritance(variant, gt.getType());
if (sn != null) checkCyclicInheritance(node, sn);
for (ClassNode in : node.getInterfaces()) {
checkCyclicInheritance(node, in);
}
if (node.getGenericsTypes() != null) {
for (GenericsType gt : node.getGenericsTypes()) {
if (gt != null && gt.getUpperBounds() != null) {
for (ClassNode variant : gt.getUpperBounds()) {
if (variant.isGenericsPlaceHolder()) checkCyclicInheritance(variant, gt.getType());
}
}
}
}
}

// VariableScopeVisitor visits anon. inner class body inline, so resolve now
for (Iterator<InnerClassNode> it = node.getInnerClasses(); it.hasNext(); ) {
InnerClassNode cn = it.next();
if (cn.isAnonymous()) {
MethodNode enclosingMethod = cn.getEnclosingMethod();
if (enclosingMethod != null) {
resolveGenericsHeader(enclosingMethod.getGenericsTypes()); // GROOVY-6977
case 2:
// VariableScopeVisitor visits anon. inner class body inline, so resolve now
for (Iterator<InnerClassNode> it = node.getInnerClasses(); it.hasNext(); ) {
InnerClassNode cn = it.next();
if (cn.isAnonymous()) {
MethodNode enclosingMethod = cn.getEnclosingMethod();
if (enclosingMethod != null) {
resolveGenericsHeader(enclosingMethod.getGenericsTypes()); // GROOVY-6977
}
resolveOrFail(cn.getUnresolvedSuperClass(false), cn); // GROOVY-9642
}
resolveOrFail(cn.getUnresolvedSuperClass(false), cn); // GROOVY-9642
}
}
// initialize scopes/variables now that imports and super types are resolved
new VariableScopeVisitor(source).visitClass(node);
if (phase == 1) break; // resolve other class headers before members, et al.

visitTypeAnnotations(node);
super.visitClass(node);
// initialize scopes/variables now that imports and super types are resolved
new VariableScopeVisitor(source).visitClass(node);

visitTypeAnnotations(node);
super.visitClass(node);
}
currentClass = oldNode;
}

@@ -0,0 +1,65 @@
/*
* 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 org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.junit.Test

class Groovy10466 {

protected field

@Test
void testClassOrder() {
def config = new CompilerConfiguration(targetDirectory: File.createTempDir())

def parentDir = File.createTempDir()
try {
def a = new File(parentDir, 'A.groovy')
a.write '''
import org.codehaus.groovy.ast.FieldNode
class A extends B {
@groovy.transform.ASTTest(value={
def mce = node.code.statements[0].expression
def var = mce.arguments[0] // "field" variable
assert var.accessedVariable instanceof FieldNode
})
void test() {
print field
}
}
'''
def b = new File(parentDir, 'B.groovy')
b.write '''
class B extends groovy.bugs.Groovy10466 {
}
'''

def loader = new GroovyClassLoader(this.class.classLoader)
def cu = new CompilationUnit(config, null, loader)
cu.addSources(a, b)
cu.compile()
} finally {
config.targetDirectory.deleteDir()
parentDir.deleteDir()
}
}
}
@@ -18,7 +18,6 @@
*/
package groovy.script

import org.junit.Ignore
import org.junit.Test

import static groovy.test.GroovyAssert.isAtLeastJdk
@@ -67,7 +66,7 @@ final class RuntimeResolveTests {
runScript('/groovy/bugs/groovy9243/Main.groovy')
}

@Test @Ignore
@Test
void testResolveOuterMemberWithoutAnImport3() {
assumeTrue(isAtLeastJdk('9.0')) // System.Logger
runScript('/groovy/bugs/groovy9866/Main.groovy')

0 comments on commit 4cbdc0f

Please sign in to comment.