[
https://issues.apache.org/jira/browse/GROOVY-12754?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18143555#comment-18143555
]
ASF GitHub Bot commented on GROOVY-12754:
-----------------------------------------
paulk-asert opened a new pull request, #3293:
URL: https://github.com/apache/groovy/pull/3293
This bundles the three open fixes for the regressions that followed
GROOVY-12714, so they can be reviewed and merged as one consistent change. It
targets master only; the `GROOVY_6_0_X` port is a separate discussion.
**_It has a "Decisions taken" section. That isn't meant to indicate that
these are decisions taken by the Groovy project, just what was put into this
PR. It roles back some of the suggestions in the referenced PRs. Groovy doesn't
try to be exactly the same as Java but has various changes to provide Groovy's
ergonomic benefits. The intention of this bundling is to make sure if we align
more closely with javac behavior on any point, we understand and document any
reduction in ergonomic benefits at the same time. It retains the original PR
commits but can be squashed as needed if accepted._**
## What is in it
- The heads of #3286 (which contains #3285) and #3289, merged with Scott's
commits unchanged.
- Five commits on top: four that change behaviour, described below, and one
that documents the rules in the type checking chapter of the guide, with spec
tests.
## Changes on top of the three PRs
**1. `getClass()` follows `instanceof` inference** (reverses 471cb3b9da)
After `o instanceof String`, `o.getClass()` and `o.class` are a `Class<?
extends String>`, like every other member of `o` in that branch. This differs
from Java and from what Daniel asked for on #3286. The reason is that
`instanceof` inference is a documented, deliberate difference from Java, and
typing one method by the declared type would be a special case inside it. It is
sound: the class of a value that passed the test is a subtype of `String`.
**2. A diamond constructor call with a wildcard argument** (regression from
a48ffb4db7)
`new Holder<>(list)` for a `List<? extends Integer>` compiles on master and
in javac, and was rejected on #3286, even for an unbounded type parameter. For
a diamond the type parameters of the class are now inferred from the arguments,
like those of a generic method. That also makes the `List<? extends String>`
form for `Holder<T extends Number>` a compile error; on master it compiles and
fails with a `GroovyCastException`.
**3. A nested wildcard for a type parameter named in a bound** (hole in
efa0808656)
For `<T extends Number, U extends List<T>> m(Class<U>)`, a `Class<? extends
ArrayList<? extends String>>` was accepted. Master and javac reject it; it is
rejected again.
**4. Target typing**
- A variable declared with a target-typed call kept the type inferred from
the arguments, so `List<Class<?>> r = Collections.singletonList(x.getClass());
return r` failed on the `return`. That compiles on master. The variable now has
its declared type.
- `List<Number> g(List<? extends Number> l) { same(l) }` with `<T extends
Number> List<T> same(List<T>)` was accepted, so a `List<Integer>` could be
returned as a `List<Number>`. Master and javac reject it; it is rejected again.
- A spread expression takes its element type from the target:
`list*.getClass()` and `list*.class` compile for a `List<Class<?>>`, as they do
on master, and `people*.name` for a `List<CharSequence>`. This is not
containment. A spread expression always makes a new list, so it is treated like
`list.collect { ... }`, which #3286 already accepts. A value that already has a
type is still not converted: a variable of type `List<Class<? extends String>>`
is not a `List<Class<?>>`.
## Decisions taken
- A `Class<?>` value, as `Class.forName` returns, stays rejected for a
`Class` with a bound, as in #3289. The guide shows `asSubclass` as the fix.
- The raw-bound rule from #3286 is kept: `? extends Map` is not within `?
extends Map<String, Object>`. A raw type as a plain type argument (`List<Map>`
for `List<Map<String, Object>>`) is still not checked. Checking that could be
an opt-in strict type checking extension, as a follow-up.
## Not changed
- `First.<Integer>first(ints)` and `Double d = first(ints)` for a `List<?
extends Integer>` still compile. Both are existing behav
- A failed generics check is an error; it does not fall back to a less
specific overload as javac would.
- Full erasure of the members of a raw type, and a declared-type rule for
`Class<ArrayList>`, are not included.
## Documentation
The "Generic types" section of the type checking chapter gains tested
examples for: a wildcard argument for a bounded type parametrom the target of a
call or spread expression, a raw type as a wildcard bound, a value that is not
a `Class` assigned to a
parameterized `Class`, and the type of `getClass()`. `COMPATIBILITY.md` is
updated for points 1 and 4.
## Testing
- Root `:test`: 20,126 tests, 0 failures. Subproject tests were not run
locally.
- Each change above has a test in `GenericsSTCTest`, so
`GenericsStaticCompileTest` runs it too.
- 27 further programs compared with javac 25.0.4: 23 agree. Of the four that
differ, three differ on master in the same way, and the fourth is the overload
fallback listed above.
Supersedes #3285, #3286 and #3289.
> STC: an upper-bounded wildcard argument is checked against a type parameter's
> bound the wrong way round
> -------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12754
> URL: https://issues.apache.org/jira/browse/GROOVY-12754
> Project: Groovy
> Issue Type: Bug
> Components: Static Type Checker
> Affects Versions: 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> Static type checking rejects a call that passes an upper-bounded wildcard,
> such as {{List<? extends Integer>}}, to a generic method whose type parameter
> is bounded by a supertype of the wildcard's bound. It accepts the call when
> the wildcard's bound is wider than the type parameter's. javac does the
> opposite in both cases.
> {code:groovy}
> @groovy.transform.CompileStatic
> class C {
> static <T extends Number> T first(List<T> list) { list.get(0) }
> static <T extends CharSequence> int len(Class<T> type) { 0 }
> static <T extends Integer> T narrow(List<T> list) { list.get(0) }
> static Object a(List<? extends Integer> ints) { first(ints) } //
> rejected, javac accepts
> static int b(Class<? extends String> type) { len(type) } //
> rejected, javac accepts
> static Object c(List<? extends Number> nums) { narrow(nums) } //
> accepted, javac rejects
> }
> {code}
> {noformat}
> [Static type checking] - Cannot call <T extends java.lang.Number>
> C#first(java.util.List<T>) with arguments [java.util.List<? extends
> java.lang.Integer>]
> [Static type checking] - Cannot call <T extends java.lang.CharSequence>
> C#len(java.lang.Class<T>) with arguments [java.lang.Class<? extends
> java.lang.String>]
> {noformat}
> The capture of {{? extends B}} is a subtype of {{B}} (JLS 5.1.10), so it is
> within the bound of {{T}} exactly when {{B}} is. javac reports "inference
> variable T has incompatible bounds" for {{c}}. Groovy compiles {{c}}, and
> {{c([1.5d])}} returns {{1}}.
> {{StaticTypeCheckingSupport#compatibleConnection}} tests a wildcard witness
> with {{connection.isCompatibleWith(bound)}}, which asks whether the type
> parameter's bound fits within {{? extends B}}. For a type argument {{B}}, it
> tests {{? super B}} against the bound. An upper-bounded wildcard should take
> the same test.
> PR: https://github.com/apache/groovy/pull/3285
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)