Skip to content

Commit 8ff2465

Browse files
committed
GROOVY-7164, GROOVY-10787
1 parent 83535fa commit 8ff2465

File tree

4 files changed

+219
-95
lines changed

4 files changed

+219
-95
lines changed

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java

+47
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,31 @@ public void testTypeChecked7128d() {
13341334
runConformTest(sources);
13351335
}
13361336

1337+
@Test
1338+
public void testTypeChecked7164() {
1339+
//@formatter:off
1340+
String[] sources = {
1341+
"Main.groovy",
1342+
"class C {\n" +
1343+
" private long timestamp\n" +
1344+
" Date getTimestamp() {\n" +
1345+
" timestamp ? new Date(timestamp) : null\n" +
1346+
" }\n" +
1347+
" void setTimestamp(Date timestamp) {\n" +
1348+
" this.timestamp = timestamp.time\n" +
1349+
" }\n" +
1350+
"}\n" +
1351+
"@groovy.transform.TypeChecked\n" +
1352+
"void test() {\n" +
1353+
" new C(timestamp: new Date())\n" +
1354+
"}\n" +
1355+
"test()\n",
1356+
};
1357+
//@formatter:on
1358+
1359+
runConformTest(sources);
1360+
}
1361+
13371362
@Test
13381363
public void testTypeChecked7247() {
13391364
//@formatter:off
@@ -6552,4 +6577,26 @@ public void testTypeChecked10767() {
65526577

65536578
runConformTest(sources);
65546579
}
6580+
6581+
@Test
6582+
public void testTypeChecked10787() {
6583+
//@formatter:off
6584+
String[] sources = {
6585+
"Main.groovy",
6586+
"abstract class A<X extends Serializable> {\n" +
6587+
" X x\n" +
6588+
"}\n" +
6589+
"class C<Y extends Serializable> extends A<Y> {\n" +
6590+
"}\n" +
6591+
"@groovy.transform.TypeChecked\n" +
6592+
"def <Z extends Number> C<Z> fn(List<Z> list_of_z) {\n" +
6593+
" new C<Z>(x: list_of_z.first())\n" +
6594+
"}\n" +
6595+
"def c = fn([42])\n" +
6596+
"assert c.x == 42\n",
6597+
};
6598+
//@formatter:on
6599+
6600+
runConformTest(sources);
6601+
}
65556602
}

base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

+66-45
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@
136136
import java.util.Set;
137137
import java.util.StringJoiner;
138138
import java.util.concurrent.atomic.AtomicLong;
139-
import java.util.concurrent.atomic.AtomicReference;
140139
import java.util.function.BiPredicate;
141140
import java.util.function.Function;
142141
import java.util.stream.IntStream;
@@ -1493,23 +1492,22 @@ protected void typeCheckAssignment(final BinaryExpression assignmentExpression,
14931492
}
14941493

14951494
protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) {
1496-
// workaround for map-style checks putting setter info on wrong AST nodes
1495+
// workaround for map-style checks putting setter info on wrong AST node
14971496
typeCheckingContext.pushEnclosingBinaryExpression(null);
14981497
for (MapEntryExpression entryExpression : mapExpression.getMapEntryExpressions()) {
14991498
Expression keyExpression = entryExpression.getKeyExpression();
15001499
if (!(keyExpression instanceof ConstantExpression)) {
15011500
addStaticTypeError("Dynamic keys in map-style constructors are unsupported in static type checking", keyExpression);
15021501
} else {
1503-
String pName = keyExpression.getText();
1504-
AtomicReference<ClassNode> pType = new AtomicReference<>();
1505-
if (!existsProperty(new PropertyExpression(varX("_", receiverType), pName), false, new PropertyLookupVisitor(pType))) {
1506-
addStaticTypeError("No such property: " + pName + " for class: " + receiverType.getText(), receiver);
1502+
String propName = keyExpression.getText();
1503+
PropertyLookup requestor = new PropertyLookup(receiverType);
1504+
if (!existsProperty(new PropertyExpression(varX("_", receiverType), propName), false, requestor)) {
1505+
addStaticTypeError("No such property: " + propName + " for class: " + prettyPrintTypeName(receiverType), receiver);
15071506
} else {
1508-
MethodNode setter = receiverType.getSetterMethod("set" + MetaClassHelper.capitalize(pName), false);
1509-
ClassNode targetType = setter != null ? setter.getParameters()[0].getType() : pType.get();
15101507
Expression valueExpression = entryExpression.getValueExpression();
1511-
ClassNode valueType = getType(valueExpression);
1508+
ClassNode valueType = getType(valueExpression);
15121509

1510+
ClassNode targetType = requestor.propertyType;
15131511
ClassNode resultType = getResultType(targetType, ASSIGN, valueType,
15141512
assignX(keyExpression, valueExpression, entryExpression));
15151513
if (!checkCompatibleAssignmentTypes(targetType, resultType, valueExpression)
@@ -1745,8 +1743,7 @@ protected boolean existsProperty(final PropertyExpression pexp, final boolean re
17451743
List<MethodNode> setters = findSetters(current, setterName, false);
17461744
setters = allowStaticAccessToMember(setters, staticOnly);
17471745

1748-
// need to visit even if we only look for a setters for compatibility
1749-
if (visitor != null && getter != null) visitor.visitMethod(getter);
1746+
if (readMode && getter != null && visitor != null) visitor.visitMethod(getter);
17501747

17511748
PropertyNode propertyNode = current.getProperty(propertyName);
17521749
propertyNode = allowStaticAccessToMember(propertyNode, staticOnly);
@@ -1771,15 +1768,11 @@ && hasAccessToMember(first(enclosingTypes), getter.getDeclaringClass(), getter.g
17711768
} else if (!readMode && checkGetterOrSetter) {
17721769
if (!setters.isEmpty()) {
17731770
if (visitor != null) {
1774-
if (field != null) {
1775-
visitor.visitField(field);
1776-
} else {
1777-
for (MethodNode setter : setters) {
1778-
// visiting setter will not infer the property type since return type is void, so visit a dummy field instead
1779-
FieldNode virtual = new FieldNode(propertyName, 0, setter.getParameters()[0].getOriginType(), current, null);
1780-
virtual.setDeclaringClass(setter.getDeclaringClass());
1781-
visitor.visitField(virtual);
1782-
}
1771+
for (MethodNode setter : setters) {
1772+
// visiting setter will not infer the property type since return type is void, so visit a dummy field instead
1773+
FieldNode virtual = new FieldNode(propertyName, 0, setter.getParameters()[0].getOriginType(), current, null);
1774+
virtual.setDeclaringClass(setter.getDeclaringClass());
1775+
visitor.visitField(virtual);
17831776
}
17841777
}
17851778
SetterInfo info = new SetterInfo(current, setterName, setters);
@@ -1923,37 +1916,29 @@ private ClassNode getTypeForSpreadExpression(ClassNode testClass, ClassNode obje
19231916
GenericsType[] gts = iteratorType.getGenericsTypes();
19241917
ClassNode itemType = (gts != null && gts.length == 1 ? getCombinedBoundType(gts[0]) : OBJECT_TYPE);
19251918

1926-
PropertyExpression subExp = new PropertyExpression(varX("{}", itemType), pexp.getPropertyAsString());
1927-
AtomicReference<ClassNode> result = new AtomicReference<>();
1928-
if (existsProperty(subExp, true, new PropertyLookupVisitor(result))) {
1929-
ClassNode listType = LIST_TYPE.getPlainNodeReference();
1930-
listType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(result.get()))});
1931-
return listType;
1919+
PropertyLookup requestor = new PropertyLookup(itemType);
1920+
if (existsProperty(new PropertyExpression(varX("{}", itemType), pexp.getPropertyAsString()), true, requestor)) {
1921+
return GenericsUtils.makeClassSafe0(LIST_TYPE, new GenericsType(wrapTypeIfNecessary(requestor.propertyType)));
19321922
}
19331923
}
19341924
}
19351925
return null;
19361926
}
19371927

19381928
private ClassNode getTypeForListPropertyExpression(ClassNode testClass, ClassNode objectExpressionType, PropertyExpression pexp) {
1939-
if (!implementsInterfaceOrIsSubclassOf(testClass, LIST_TYPE)) return null;
1940-
/* GRECLIPSE edit -- GROOVY-9821
1941-
ClassNode intf = GenericsUtils.parameterizeType(objectExpressionType, LIST_TYPE.getPlainNodeReference());
1942-
GenericsType[] types = intf.getGenericsTypes();
1943-
if (types == null || types.length != 1) return OBJECT_TYPE;
1944-
1945-
PropertyExpression subExp = new PropertyExpression(varX("{}", types[0].getType()), pexp.getPropertyAsString());
1946-
*/
1947-
GenericsType[] types = (testClass.equals(LIST_TYPE) ? testClass : GenericsUtils.parameterizeType(testClass, LIST_TYPE)).getGenericsTypes();
1948-
ClassNode itemType = (types != null && types.length == 1 ? getCombinedBoundType(types[0]) : OBJECT_TYPE);
1949-
1950-
PropertyExpression subExp = new PropertyExpression(varX("{}", itemType), pexp.getPropertyAsString());
1951-
// GRECLIPSE end
1952-
AtomicReference<ClassNode> result = new AtomicReference<ClassNode>();
1953-
if (existsProperty(subExp, true, new PropertyLookupVisitor(result))) {
1954-
ClassNode intf = LIST_TYPE.getPlainNodeReference();
1955-
intf.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(result.get()))});
1956-
return intf;
1929+
if (implementsInterfaceOrIsSubclassOf(testClass, LIST_TYPE)) {
1930+
/* GRECLIPSE edit -- GROOVY-9821
1931+
ClassNode listType = GenericsUtils.parameterizeType(objectExpressionType, LIST_TYPE.getPlainNodeReference());
1932+
GenericsType[] gts = listType.getGenericsTypes();
1933+
ClassNode itemType = (gts != null && gts.length == 1 ? gts[0].getType() : OBJECT_TYPE);
1934+
*/
1935+
GenericsType[] gts = (testClass.equals(LIST_TYPE) ? testClass : GenericsUtils.parameterizeType(testClass, LIST_TYPE)).getGenericsTypes();
1936+
ClassNode itemType = (gts != null && gts.length == 1 ? getCombinedBoundType(gts[0]) : OBJECT_TYPE);
1937+
// GRECLIPSE end
1938+
PropertyLookup requestor = new PropertyLookup(itemType);
1939+
if (existsProperty(new PropertyExpression(varX("{}", itemType), pexp.getPropertyAsString()), true, requestor)) {
1940+
return GenericsUtils.makeClassSafe0(LIST_TYPE, new GenericsType(wrapTypeIfNecessary(requestor.propertyType)));
1941+
}
19571942
}
19581943
return null;
19591944
}
@@ -6792,12 +6777,48 @@ private SetterInfo(final ClassNode receiverType, final String name, final List<M
67926777
}
67936778
}
67946779

6780+
private class PropertyLookup extends ClassCodeVisitorSupport {
6781+
ClassNode propertyType, receiverType;
6782+
6783+
PropertyLookup(final ClassNode type) {
6784+
receiverType = type;
6785+
}
6786+
6787+
@Override
6788+
protected SourceUnit getSourceUnit() {
6789+
return StaticTypeCheckingVisitor.this.getSourceUnit();
6790+
}
6791+
6792+
@Override
6793+
public void visitField(final FieldNode node) {
6794+
storePropertyType(node.getType(), node.isStatic() ? null : node.getDeclaringClass());
6795+
}
6796+
6797+
@Override
6798+
public void visitMethod(final MethodNode node) {
6799+
storePropertyType(node.getReturnType(), node.isStatic() ? null : node.getDeclaringClass());
6800+
}
6801+
6802+
@Override
6803+
public void visitProperty(final PropertyNode node) {
6804+
storePropertyType(node.getOriginType(), node.isStatic() ? null : node.getDeclaringClass());
6805+
}
6806+
6807+
private void storePropertyType(ClassNode type, final ClassNode declaringClass) {
6808+
if (declaringClass != null && GenericsUtils.hasUnresolvedGenerics(type)) { // GROOVY-10787
6809+
Map<GenericsTypeName, GenericsType> spec = extractPlaceHolders(null, receiverType, declaringClass);
6810+
type = applyGenericsContext(spec, type);
6811+
}
6812+
// TODO: if (propertyType != null) merge types
6813+
propertyType = type;
6814+
}
6815+
}
6816+
67956817
/**
67966818
* Wrapper for a Parameter so it can be treated like a VariableExpression
67976819
* and tracked in the {@code ifElseForWhileAssignmentTracker}.
67986820
*/
67996821
private class ParameterVariableExpression extends VariableExpression {
6800-
68016822
private final Parameter parameter;
68026823

68036824
ParameterVariableExpression(final Parameter parameter) {

base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

+53-26
Original file line numberDiff line numberDiff line change
@@ -1459,23 +1459,22 @@ else if (rightExpression instanceof MapExpression)
14591459
}
14601460

14611461
protected void checkGroovyConstructorMap(final Expression receiver, final ClassNode receiverType, final MapExpression mapExpression) {
1462-
// workaround for map-style checks putting setter info on wrong AST nodes
1462+
// workaround for map-style checks putting setter info on wrong AST node
14631463
typeCheckingContext.pushEnclosingBinaryExpression(null);
14641464
for (MapEntryExpression entryExpression : mapExpression.getMapEntryExpressions()) {
14651465
Expression keyExpression = entryExpression.getKeyExpression();
14661466
if (!(keyExpression instanceof ConstantExpression)) {
14671467
addStaticTypeError("Dynamic keys in map-style constructors are unsupported in static type checking", keyExpression);
14681468
} else {
1469-
String pName = keyExpression.getText();
1470-
AtomicReference<ClassNode> pType = new AtomicReference<>();
1471-
if (!existsProperty(propX(varX("_", receiverType), pName), false, new PropertyLookupVisitor(pType))) {
1472-
addStaticTypeError("No such property: " + pName + " for class: " + receiverType.getText(), receiver);
1469+
String propName = keyExpression.getText();
1470+
PropertyLookup requestor = new PropertyLookup(receiverType);
1471+
if (!existsProperty(propX(varX("_", receiverType), propName), false, requestor)) {
1472+
addStaticTypeError("No such property: " + propName + " for class: " + prettyPrintTypeName(receiverType), receiver);
14731473
} else {
1474-
ClassNode targetType = Optional.ofNullable(receiverType.getSetterMethod(getSetterName(pName), false))
1475-
.map(setter -> setter.getParameters()[0].getType()).orElseGet(pType::get);
14761474
Expression valueExpression = entryExpression.getValueExpression();
1477-
ClassNode valueType = getType(valueExpression);
1475+
ClassNode valueType = getType(valueExpression);
14781476

1477+
ClassNode targetType = requestor.propertyType;
14791478
ClassNode resultType = getResultType(targetType, ASSIGN, valueType,
14801479
assignX(keyExpression, valueExpression, entryExpression));
14811480
if (!checkCompatibleAssignmentTypes(targetType, resultType, valueExpression)
@@ -1704,8 +1703,7 @@ protected boolean existsProperty(final PropertyExpression pexp, final boolean re
17041703
List<MethodNode> setters = findSetters(current, setterName, false);
17051704
setters = allowStaticAccessToMember(setters, staticOnly);
17061705

1707-
// need to visit even if we only look for setters for compatibility
1708-
if (visitor != null && getter != null) visitor.visitMethod(getter);
1706+
if (readMode && getter != null && visitor != null) visitor.visitMethod(getter);
17091707

17101708
PropertyNode property = current.getProperty(propertyName);
17111709
property = allowStaticAccessToMember(property, staticOnly);
@@ -1726,15 +1724,11 @@ && hasAccessToMember(enclosingTypes.iterator().next(), getter.getDeclaringClass(
17261724
} else {
17271725
if (!setters.isEmpty()) {
17281726
if (visitor != null) {
1729-
if (field != null) {
1730-
visitor.visitField(field);
1731-
} else {
1732-
for (MethodNode setter : setters) {
1733-
// visiting setter will not infer the property type since return type is void, so visit a dummy field instead
1734-
FieldNode virtual = new FieldNode(propertyName, 0, setter.getParameters()[0].getOriginType(), current, null);
1735-
virtual.setDeclaringClass(setter.getDeclaringClass());
1736-
visitor.visitField(virtual);
1737-
}
1727+
for (MethodNode setter : setters) {
1728+
// visiting setter will not infer the property type since return type is void, so visit a dummy field instead
1729+
FieldNode virtual = new FieldNode(propertyName, 0, setter.getParameters()[0].getOriginType(), current, null);
1730+
virtual.setDeclaringClass(setter.getDeclaringClass());
1731+
visitor.visitField(virtual);
17381732
}
17391733
}
17401734
SetterInfo info = new SetterInfo(current, getSetterName(propertyName), setters);
@@ -1877,12 +1871,9 @@ private ClassNode getTypeForMultiValueExpression(final ClassNode compositeType,
18771871
GenericsType[] gts = compositeType.getGenericsTypes();
18781872
ClassNode itemType = (gts != null && gts.length == 1 ? getCombinedBoundType(gts[0]) : OBJECT_TYPE);
18791873

1880-
AtomicReference<ClassNode> propertyType = new AtomicReference<>();
1881-
if (existsProperty(propX(varX("{}", itemType), prop), true, new PropertyLookupVisitor(propertyType))) {
1882-
gts = new GenericsType[]{new GenericsType(wrapTypeIfNecessary(propertyType.get()))};
1883-
ClassNode listType = LIST_TYPE.getPlainNodeReference();
1884-
listType.setGenericsTypes(gts);
1885-
return listType;
1874+
PropertyLookup requestor = new PropertyLookup(itemType);
1875+
if (existsProperty(propX(varX("{}", itemType), prop), true, requestor)) {
1876+
return GenericsUtils.makeClassSafe0(LIST_TYPE, new GenericsType(wrapTypeIfNecessary(requestor.propertyType)));
18861877
}
18871878
return null;
18881879
}
@@ -6531,12 +6522,48 @@ private SetterInfo(final ClassNode receiverType, final String name, final List<M
65316522
}
65326523
}
65336524

6525+
private class PropertyLookup extends ClassCodeVisitorSupport {
6526+
ClassNode propertyType, receiverType;
6527+
6528+
PropertyLookup(final ClassNode type) {
6529+
receiverType = type;
6530+
}
6531+
6532+
@Override
6533+
protected SourceUnit getSourceUnit() {
6534+
return StaticTypeCheckingVisitor.this.getSourceUnit();
6535+
}
6536+
6537+
@Override
6538+
public void visitField(final FieldNode node) {
6539+
storePropertyType(node.getType(), node.isStatic() ? null : node.getDeclaringClass());
6540+
}
6541+
6542+
@Override
6543+
public void visitMethod(final MethodNode node) {
6544+
storePropertyType(node.getReturnType(), node.isStatic() ? null : node.getDeclaringClass());
6545+
}
6546+
6547+
@Override
6548+
public void visitProperty(final PropertyNode node) {
6549+
storePropertyType(node.getOriginType(), node.isStatic() ? null : node.getDeclaringClass());
6550+
}
6551+
6552+
private void storePropertyType(ClassNode type, final ClassNode declaringClass) {
6553+
if (declaringClass != null && GenericsUtils.hasUnresolvedGenerics(type)) { // GROOVY-10787
6554+
Map<GenericsTypeName, GenericsType> spec = extractPlaceHolders(receiverType, declaringClass);
6555+
type = applyGenericsContext(spec, type);
6556+
}
6557+
// TODO: if (propertyType != null) merge types
6558+
propertyType = type;
6559+
}
6560+
}
6561+
65346562
/**
65356563
* Wrapper for a Parameter so it can be treated like a VariableExpression
65366564
* and tracked in the {@code ifElseForWhileAssignmentTracker}.
65376565
*/
65386566
private class ParameterVariableExpression extends VariableExpression {
6539-
65406567
private final Parameter parameter;
65416568

65426569
ParameterVariableExpression(final Parameter parameter) {

0 commit comments

Comments
 (0)