Paul King created GROOVY-12486:
----------------------------------

             Summary: 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


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