diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java index d0b17f0161c..1d44373d968 100644 --- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java +++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -432,7 +432,7 @@ public static ClassNode correctToGenericsSpecRecurse(Map gene newgTypes[i] = fixed; } else if (oldgType.isPlaceholder()) { // correct "T" - newgTypes[i] = new GenericsType(genericsSpec.getOrDefault(oldgType.getName(), ClassHelper.OBJECT_TYPE)); + newgTypes[i] = genericsSpec.containsKey(oldgType.getName())? new GenericsType(genericsSpec.get(oldgType.getName())): erasure(oldgType); } else { // correct "List", etc. newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions)); 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 2c37fceade0..a3621e9d730 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -3850,10 +3850,8 @@ protected List> makeOwnerList(final Expression objectExpression } else { List temporaryTypes = getTemporaryTypesForExpression(objectExpression); int temporaryTypesCount = (temporaryTypes != null ? temporaryTypes.size() : 0); - if (temporaryTypesCount > 0) { - // GROOVY-8965, GROOVY-10180, GROOVY-10668 - ClassNode commonType = lowestUpperBound(temporaryTypes); - if (!commonType.equals(OBJECT_TYPE)) owners.add(Receiver.make(commonType)); + if (temporaryTypesCount > 0) { // GROOVY-8965, GROOVY-10180, GROOVY-10668 + owners.add(Receiver.make(lowestUpperBound(temporaryTypes))); } if (isClassClassNodeWrappingConcreteType(receiver)) { ClassNode staticType = receiver.getGenericsTypes()[0].getType(); @@ -3877,7 +3875,7 @@ protected List> makeOwnerList(final Expression objectExpression && ((Variable) objectExpression).getName().equals("it")) { owners.add(Receiver.make(typeCheckingContext.lastImplicitItType)); } - if (temporaryTypesCount > 1) { + if (temporaryTypesCount > 1 && !(objectExpression instanceof VariableExpression)) { owners.add(Receiver.make(new UnionTypeClassNode(temporaryTypes.toArray(ClassNode.EMPTY_ARRAY)))); } } diff --git a/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java b/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java index bff30695e8b..b4aa4154e06 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/UnionTypeClassNode.java @@ -20,7 +20,6 @@ import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotationNode; -import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; @@ -35,6 +34,7 @@ import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.transform.ASTTransformation; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -45,6 +45,10 @@ import java.util.Set; import java.util.StringJoiner; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; +import static org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode; + /** * This class node type is very special and should only be used by the static type checker * to represent types which are the union of other types. This is useful when, for example, @@ -59,8 +63,9 @@ class UnionTypeClassNode extends ClassNode { private final ClassNode[] delegates; UnionTypeClassNode(final ClassNode... classNodes) { - super(makeName(classNodes), 0, ClassHelper.OBJECT_TYPE); - delegates = classNodes == null ? ClassNode.EMPTY_ARRAY : classNodes; + super(makeName(classNodes), 0, makeSuper(classNodes)); + delegates = classNodes; + isPrimaryNode = false; } private static String makeName(final ClassNode[] nodes) { @@ -71,6 +76,18 @@ private static String makeName(final ClassNode[] nodes) { return sj.toString(); } + private static ClassNode makeSuper(final ClassNode[] nodes) { + ClassNode upper = lowestUpperBound(Arrays.asList(nodes)); + if (upper instanceof LowestUpperBoundClassNode) { + upper = upper.getUnresolvedSuperClass(); + } else if (upper.isInterface()) { + upper = OBJECT_TYPE; + } + return upper; + } + + //-------------------------------------------------------------------------- + public ClassNode[] getDelegates() { return delegates; } diff --git a/src/test/groovy/bugs/Groovy11250.groovy b/src/test/groovy/bugs/Groovy11250.groovy new file mode 100644 index 00000000000..e1fbf071e05 --- /dev/null +++ b/src/test/groovy/bugs/Groovy11250.groovy @@ -0,0 +1,132 @@ +/* + * 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.CompilerConfiguration +import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit +import org.junit.Test + +final class Groovy11250 { + + @Test + void testMultipleInstanceOfPropertyAccess() { + def config = new CompilerConfiguration().tap { + jointCompilationOptions = [memStub: true] + targetDirectory = File.createTempDir() + } + File parentDir = File.createTempDir() + try { + def a = new File(parentDir, 'Property.java') + a.write ''' + public class Property { + private String targetName; + private String propertyName; + public String getName() { return propertyName; } + public String getTargetName() { return targetName; } + public void setTargetName(String targetName) { this.targetName = targetName; } + public void setPropertyName(String propertyName) { this.propertyName = propertyName; } + } + ''' + + def b = new File(parentDir, 'PersistentProperty.java') + b.write ''' + public interface PersistentProperty { + String getName(); + } + ''' + + def c = new File(parentDir, 'AbstractPersistentProperty.java') + c.write ''' + public abstract class AbstractPersistentProperty implements PersistentProperty { + protected final String name; + public AbstractPersistentProperty(String name) { + this.name = name; + } + @Override + public String getName() { + return name; + } + } + ''' + + def d = new File(parentDir, 'Association.java') + d.write ''' + public abstract class Association extends AbstractPersistentProperty { + public Association(String name) { + super(name); + } + } + ''' + + def e = new File(parentDir, 'Main.groovy') + e.write '''import groovy.transform.CompileStatic + @CompileStatic + class Attribute extends Property { + } + @CompileStatic + abstract class ToOne extends Association { + ToOne(String name) { super(name) } + } + @CompileStatic + abstract class ToMany extends Association { + ToMany(String name) { super(name) } + } + + @CompileStatic + abstract class OneToOne extends ToOne { + OneToOne(String name) { super(name) } + } + @CompileStatic + abstract class OneToMany extends ToMany { + OneToMany(String name) { super(name) } + } + @CompileStatic + abstract class ManyToMany extends ToMany { + ManyToMany(String name) { super(name) } + } + + @CompileStatic + static main(args) { + def oneToOne = new OneToOne('foo') {} + def oneToMany = new OneToMany('bar') {} + for (association in [oneToOne, oneToMany]) { + if (association instanceof ToOne) { + def propertyName = association.name + println "to-one -> $propertyName" + } else if (association instanceof OneToMany || association instanceof ManyToMany) { + def associationName = association.getName() + def propertyName = association.name + println "to-many -> $propertyName" + } + } + } + ''' + + def loader = new GroovyClassLoader(this.class.classLoader) + def cu = new JavaAwareCompilationUnit(config, loader) + cu.addSources(a, b, c, d, e) + cu.compile() + + loader.loadClass('Main').main() + } finally { + parentDir.deleteDir() + config.targetDirectory.deleteDir() + } + } +}