Skip to content

Commit

Permalink
GROOVY-11250: UnionTypeClassNode super class
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Dec 18, 2023
1 parent 47100b0 commit 2e5cb2a
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public static ClassNode correctToGenericsSpecRecurse(Map<String, ClassNode> 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<T>", etc.
newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3850,10 +3850,8 @@ protected List<Receiver<String>> makeOwnerList(final Expression objectExpression
} else {
List<ClassNode> 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();
Expand All @@ -3877,7 +3875,7 @@ protected List<Receiver<String>> 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))));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down
132 changes: 132 additions & 0 deletions src/test/groovy/bugs/Groovy11250.groovy
Original file line number Diff line number Diff line change
@@ -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<T extends Property> {
String getName();
}
'''

def c = new File(parentDir, 'AbstractPersistentProperty.java')
c.write '''
public abstract class AbstractPersistentProperty<T extends Property> implements PersistentProperty<T> {
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<T extends Property> extends AbstractPersistentProperty<T> {
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<T extends Property> extends Association<T> {
ToOne(String name) { super(name) }
}
@CompileStatic
abstract class ToMany<T extends Property> extends Association<T> {
ToMany(String name) { super(name) }
}
@CompileStatic
abstract class OneToOne<T extends Property> extends ToOne<T> {
OneToOne(String name) { super(name) }
}
@CompileStatic
abstract class OneToMany<T extends Property> extends ToMany<T> {
OneToMany(String name) { super(name) }
}
@CompileStatic
abstract class ManyToMany<T extends Property> extends ToMany<T> {
ManyToMany(String name) { super(name) }
}
@CompileStatic
static main(args) {
def oneToOne = new OneToOne<Attribute>('foo') {}
def oneToMany = new OneToMany<Attribute>('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()
}
}
}

0 comments on commit 2e5cb2a

Please sign in to comment.