[ 
https://issues.apache.org/jira/browse/GROOVY-12486?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18132044#comment-18132044
 ] 

ASF GitHub Bot commented on GROOVY-12486:
-----------------------------------------

paulk-asert commented on PR #3020:
URL: https://github.com/apache/groovy/pull/3020#issuecomment-6407704164

   Thanks Daniel, and good question on point 4. Looking at it again turned up a 
regression, now fixed in the latest push.
   
   On ordering: the checker's bindName follows the runtime dispatcher exactly 
(Comprehensions.resolveMethodName): registry entries (Class-keyed, then 
name-keyed), then a structural single-arg flatMap, then @Monadic(bind=...). So 
a class with both a flatMap and @Monadic(bind='chain') binds via flatMap in 
both places.
   
   On point 3 and point 4: the bound type's position doesn't matter when the 
bind signature is readable. It's resolved from the bind function's input (`? 
super A`) whatever slot A occupies, which the @Monadic Tri<E, A, X> test shows. 
Position only matters in the fallback, used when the bind parameter names no 
input: a Closure, or a raw Function or fj.F. In that case, falling back to the 
last type argument regressed carriers like `Box<A, M>` with an untyped 
`flatMap(Closure)`, which compiled before this PR. The fallback is now the 
first type argument, as before, so this PR only changes typing when the bind 
signature gives the answer.
   
   New tests:
   - the fallback for a Closure bind and for a raw Function bind, with the 
bound type first (both fail with the last-argument fallback)
   - a bind declared as an interface default method, on a receiver whose type 
parameters are reordered
   - a wildcard receiver, Result<String, ? extends Integer>
   - a name-keyed fj.data.Validation<E, T> stub, as suggested on the JIRA
   
   The bind-input tests all fail against the checker before this PR. 
groovy-macro-library 62/0, groovy-typecheckers 376/0, lintGate clean.
   
   A Closure-typed bind with the bound type last, such as GRes<E, A>, still 
uses the fallback, as it did before. Static type checking already reads 
@ClosureParams for a direct call to such a bind, so DO should too. That's 
GROOVY-12531 for Groovy 7. It needs a decision on how @ClosureParams ranks 
against the bound-type information that GEP-23's carrier registration may add, 
so I've kept it out of this PR.
   
   This affects the 6.0.0 allow-list rows (Vavr Either/Validation, Functional 
Java Validation) and cherry-picks cleanly onto GROOVY_6_0_X, so I plan to 
backport it to 6.0.1. GROOVY-12485 (the Zazr rows) builds on it: Zazr Either 
and Validation bind on their last type parameter.




> MonadicChecker types the DO generator variable from the carrier's first type 
> argument
> -------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12486
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12486
>             Project: Groovy
>          Issue Type: Bug
>          Components: groovy-typecheckers
>            Reporter: Paul King
>            Priority: Major
>
> h2. Summary
> Under {{MonadicChecker}}, the variable bound by a {{DO}} generator is typed 
> as the carrier's *first* type argument. For carriers with more than one type 
> parameter that bind on a later one, the variable gets the wrong type. 
> Examples are an {{Either<L, R>}} or {{Validation<E, A>}} that is 
> right-biased. The result is a spurious type error, or code that compiles only 
> by accident.
> This affects carriers already on the 6.0.0 allow-list: Vavr {{Either}} and 
> {{Validation}}, and Functional Java {{Validation}}. It also affects 
> structural and {{@Monadic}} carriers of the same shape.
> h2. Reproducer
> {code:groovy}
> import groovy.transform.TypeChecked
> import java.util.function.Function
> import static org.apache.groovy.macrolib.MacroLibGroovyMethods.DO
> class Result<E, A> {
>     final A value
>     Result(A value) { this.value = value }
>     static <E, A> Result<E, A> ok(A value) { new Result<E, A>(value) }
>     def <B> Result<E, B> flatMap(Function<? super A, ? extends Result<E, B>> 
> f) { f.apply(value) }
>     def <B> Result<E, B> map(Function<? super A, ? extends B> f) { 
> ok(f.apply(value)) }
>     String toString() { "Ok($value)" }
> }
> @TypeChecked(extensions = 'groovy.typecheckers.MonadicChecker')
> def f() {
>     DO(x in Result.<String, Integer>ok(1)) {
>         int n = x
>         Result.<String, Integer>ok(n + 1)
>     }
> }
> println f()
> {code}
> h2. Actual (6.0.0)
> {noformat}
> [Static type checking] - Cannot assign value of type java.lang.String to 
> variable of type int
> {noformat}
> {{x}} is typed as {{E}} ({{String}}). It should be {{A}} ({{Integer}}), the 
> parameter type of {{flatMap}}'s function argument.
> The same happens with a real right-biased {{Either}}. With an uninferred left 
> type, e.g. {{DO(x in Either.right(1)) \{ Either.right(x + 1) \}}}, the error 
> is:
> {noformat}
> [Static type checking] - Cannot find matching method #L#plus(int)
> {noformat}
> When the type arguments are explicit, code such as {{x + 1}} still compiles, 
> because it resolves to {{String.plus}}. It then works at runtime only because 
> the actual value is an {{Integer}}.
> h2. Expected
> {{x}} is typed as {{Integer}}, the reproducer compiles, and it prints 
> {{Ok(2)}}.
> h2. Cause and suggested fix
> {{MonadicChecker.elementType}} returns {{carrier.genericsTypes[0]}}. That is 
> right for single-parameter carriers ({{Optional}}, {{Stream}}, 
> {{CompletionStage}}, {{Awaitable}}) but not in general.
> The element type should come from the carrier's bind method. Find the 
> single-argument method named by the carrier's bind entry ({{flatMap}}, or 
> {{bind}} for Functional Java, or the {{@Monadic}} value). Take the first type 
> argument of its function parameter ({{? super A}}) and resolve it against the 
> receiver's type arguments. Fall back to the last type argument, then 
> {{Object}}, when that can't be determined, e.g. for a raw receiver.
> h2. Tests
> * A two-parameter structural carrier, as in the reproducer.
> * A {{@Monadic}} carrier whose type parameters are declared in a different 
> order from the bound one.
> * A name-keyed carrier, using a test stub in the style of 
> {{fj/data/Option.groovy}}, e.g. a {{Validation<E, A>}} stub.
> * Regression check that single-parameter carriers ({{Optional}}, {{Stream}}, 
> {{CompletableFuture}}) keep their current typing.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to