Repository: incubator-groovy Updated Branches: refs/heads/GROOVY_2_4_X f3c96fc06 -> 0e9e50591
GROOVY-7538: Bound unbounded wildcards using the type declaration (closes #197) If there is a generic type declared with bounds, for example class NumberList<T extends Number> implements List<T> but used with an unbounded wildcard NumberList<?> getList() the bounds are actually implicit and need to be taken into account for the static type checks, to be able to use the type of the parameter directly: getList()[0].longValue() The type of the element is known to be Number, not Object. Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/0e9e5059 Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/0e9e5059 Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/0e9e5059 Branch: refs/heads/GROOVY_2_4_X Commit: 0e9e5059155d8d33ae78756699dc8a6db47cef4d Parents: f3c96fc Author: Frank Pavageau <fpavag...@ekino.com> Authored: Thu Nov 26 16:12:09 2015 +0100 Committer: pascalschumacher <pascalschumac...@gmx.net> Committed: Sat Nov 28 20:58:36 2015 +0100 ---------------------------------------------------------------------- .../stc/StaticTypeCheckingSupport.java | 48 ++++++++++++++++++++ .../stc/StaticTypeCheckingVisitor.java | 4 +- .../groovy/transform/stc/GenericsSTCTest.groovy | 2 +- .../classgen/asm/sc/bugs/Groovy7538Bug.groovy | 3 -- 4 files changed, 52 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0e9e5059/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 7e0aa4a..4760cb3 100644 --- a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -1804,6 +1804,54 @@ public abstract class StaticTypeCheckingSupport { return map; } + /** + * Apply the bounds from the declared type when the using type simply declares a parameter as an unbounded wildcard. + * + * @param type A parameterized type + * @return A parameterized type with more precise wildcards + */ + static ClassNode boundUnboundedWildcards(ClassNode type) { + if (type.isArray()) { + return boundUnboundedWildcards(type.getComponentType()).makeArray(); + } + ClassNode target = type.redirect(); + if (target == null || type == target || !isUsingGenericsOrIsArrayUsingGenerics(target)) return type; + ClassNode newType = type.getPlainNodeReference(); + newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder()); + newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), target.getGenericsTypes())); + return newType; + } + + private static GenericsType[] boundUnboundedWildcards(GenericsType[] usage, GenericsType[] declaration) { + GenericsType[] newGts = new GenericsType[usage.length]; + for (int i = 0; i < usage.length; i++) { + newGts[i] = boundUnboundedWildcard(usage[i], declaration[i]); + } + return newGts; + } + + private static GenericsType boundUnboundedWildcard(GenericsType gt, GenericsType spec) { + if (isUnboundedWildcard(gt)) { + ClassNode base = ClassHelper.makeWithoutCaching("?"); + // The bounds on the declared type are at least as good as the ones on an unbounded wildcard, since it has + // none! + GenericsType newGt = new GenericsType(base, spec.getUpperBounds(), spec.getLowerBound()); + newGt.setWildcard(true); + return newGt; + } + return gt; + } + + private static boolean isUnboundedWildcard(GenericsType gt) { + if (gt.isWildcard() && gt.getLowerBound() == null) { + ClassNode[] upperBounds = gt.getUpperBounds(); + return upperBounds == null || + upperBounds.length == 0 || + (upperBounds.length == 1 && OBJECT_TYPE.equals(upperBounds[0])); + } + return false; + } + static Map<String, GenericsType> extractGenericsParameterMapOfThis(MethodNode mn) { if (mn==null) return null; Map<String, GenericsType> map = getGenericsParameterMapOfThis(mn.getDeclaringClass()); http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0e9e5059/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index ab5c65f..a5bde88 100644 --- a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -4216,7 +4216,9 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { GenericsUtils.extractPlaceholders(receiver, resolvedPlaceholders); } resolvePlaceholdersFromExplicitTypeHints(method, explicitTypeHints, resolvedPlaceholders); - if (resolvedPlaceholders.isEmpty()) return returnType; + if (resolvedPlaceholders.isEmpty()) { + return boundUnboundedWildcards(returnType); + } Map<String, GenericsType> placeholdersFromContext = extractGenericsParameterMapOfThis(typeCheckingContext.getEnclosingMethod()); applyGenericsConnections(placeholdersFromContext,resolvedPlaceholders); http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0e9e5059/src/test/groovy/transform/stc/GenericsSTCTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy index f69e55a..6b3ab57 100644 --- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy @@ -494,7 +494,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } } new ClassB() - ''', 'Cannot call <X> groovy.transform.stc.GenericsSTCTest$ClassA <Long>#bar(java.lang.Class <Long>) with arguments [java.lang.Class <java.lang.Object extends java.lang.Object>]' + ''', 'Cannot call <X> groovy.transform.stc.GenericsSTCTest$ClassA <Long>#bar(java.lang.Class <Long>) with arguments [java.lang.Class <? extends java.lang.Object>]' } // GROOVY-5516 http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/0e9e5059/src/test/org/codehaus/groovy/classgen/asm/sc/bugs/Groovy7538Bug.groovy ---------------------------------------------------------------------- diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/bugs/Groovy7538Bug.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/bugs/Groovy7538Bug.groovy index 22feb69..d07caf3 100644 --- a/src/test/org/codehaus/groovy/classgen/asm/sc/bugs/Groovy7538Bug.groovy +++ b/src/test/org/codehaus/groovy/classgen/asm/sc/bugs/Groovy7538Bug.groovy @@ -18,19 +18,16 @@ */ package org.codehaus.groovy.classgen.asm.sc.bugs -import groovy.transform.NotYetImplemented import groovy.transform.stc.StaticTypeCheckingTestCase import org.codehaus.groovy.classgen.asm.sc.StaticCompilationTestSupport class Groovy7538Bug extends StaticTypeCheckingTestCase implements StaticCompilationTestSupport { - @NotYetImplemented void testFluentSubTypeToSuperType() { assertScript '''import org.codehaus.groovy.classgen.asm.sc.bugs.support.Groovy7538Support Groovy7538Support.assertThat("true").isNotEmpty().isNotEqualTo("false") ''' } - @NotYetImplemented void testFluentSuperTypeToSubType() { assertScript '''import org.codehaus.groovy.classgen.asm.sc.bugs.support.Groovy7538Support Groovy7538Support.assertThat("true").isNotEqualTo("false").isNotEmpty()