Question regarding generic functions' type inference

60 views
Skip to first unread message

Francesco Gaudenzi (Gaudo)

unread,
Nov 14, 2023, 12:31:21 PM11/14/23
to Dart Misc
Consider the following code:

A2 pipe2<A1, A2>(
A1 a1,
A2 Function(A1) f1,
) =>
f1(a1);

String Function(E) ask<E>() => (env) => 'hello';

String Function(E) Function<E>(String Function(E)) map(
String Function(String) f,
) =>
<E>(r) => (env) => f(r(env));

void main() {
pipe2(
ask<int>(),
map((b) => 'world'),
);
}

If i run the analyzer i get:

Analyzing common.dart...               0.3s

  error • common.dart:19:3 • Couldn't infer type parameter 'A1'.
         
          Tried to
          infer 'String Function(int)' for 'A1' which doesn't work:
            Parameter
          'f1' declared as     'A2 Function(A1)'
                           but argument
          is 'String Function(dynamic) Function(String Function(dynamic))'.
          The
          type 'String Function(int)' was inferred from:
            Parameter 'a1'
          declared as     'A1'
                           but argument is 'String
          Function(int)'.
         
          Consider passing explicit type argument(s) to the
          generic.
         
           • could_not_infer
  error • common.dart:21:5 • The argument type 'String Function(dynamic)
          Function(String Function(dynamic))' can't be assigned to the parameter
          type 'String Function(dynamic) Function(String Function(int))'. •
          argument_type_not_assignable
warning • common.dart:21:5 • The type argument(s) of the generic function
          type 'String Function(E) Function<E>(String Function(E))' can't be
          inferred. Use explicit type argument(s) for 'String Function(E)
          Function<E>(String Function(E))'. •
          inference_failure_on_generic_invocation

3 issues found.


But If i change the main to:

void main() {
pipe2(
ask<int>(),
(a) => map((b) => 'world')(a),
);
}

Then the analyzer stops complaining.

Why is the analyzer able to infer the type of the generic function returned by map only in the latter case?
Is there another way to specify such type without introducing the local variable 'a'?

Samuel Rawlins

unread,
Nov 14, 2023, 12:42:38 PM11/14/23
to mi...@dartlang.org
The map function is making my head spin, but while I'm looking, can you offer two more pieces of information:

What version of Dart are you running? In May, 2022, the language team supported (I think a limited style of) horizontal inference: language#731. Not sure what the first version of Dart is that features this.

Also, can you run the code, which the analyzer complains about? The analyzer's behavior in reporting errors should be an exact match to the compiler's behavior. So if that code runs even with the analyzer complaining, we have an analyzer bug.

--
For more ways to connect visit https://dart.dev/community
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.
To view this discussion on the web visit https://groups.google.com/a/dartlang.org/d/msgid/misc/243b9f9f-685f-4609-9e79-6fced62a8a90n%40dartlang.org.

Francesco Gaudenzi (Gaudo)

unread,
Nov 14, 2023, 1:17:03 PM11/14/23
to Dart Misc, Samuel Rawlins
Hello Samuel,

I'm using dart 3.1.5.
The code is in a new project with all the strictness flags turned on.

In both cases i can run the code just fine.

I should mention that yesterday i opened issue https://github.com/dart-lang/sdk/issues/54011 and it was marked as a bug, but it's a different example showing a different problem.
In that github issue, the dart analyzer remains silent, but running the code reports a compile-time error.

In this specific case it's the opposite: the code runs but the analyzer complains. Not sure if it's a bug.

Francesco Gaudenzi

unread,
Nov 14, 2023, 1:31:42 PM11/14/23
to Modestas Valauskas, Dart Misc
void main() {
pipe2(
ask<int>(),
map((b) => 'world')<int>,
);
}

This could be a way to solve the problem without introducing the local variable and maintaining the return function of map generic.
But I would prefer the type to be inferred automatically.
Even if pipe expects a non-generic function, the analyzer should still be able to figure out the returning type of the generic function based on the first parameter.

On Tue, 14 Nov 2023 at 18:54, Modestas Valauskas <valauska...@gmail.com> wrote:
You could do the following:

A2 pipe2<A1, A2>(
A1 a1,
A2 Function(A1) f1,
) =>
f1(a1);

String Function(E) ask<E>() => (env) => 'hello';

String Function(E) Function<E>(String Function(E)) map(
String Function(String) f,
) =>
<E>(r) => (env) => f(r(env));

void main() {
pipe2(
ask<int>(),
map((b) => 'world')<int>,
);
}

That is, to instantiate your map function with a trailing type argument without actually calling it (this is a relatively new feature).

You could also lift the type parameter E, and specify a type argument like you did with ask:

A2 pipe2<A1, A2>(
A1 a1,
A2 Function(A1) f1,
) =>
f1(a1);

String Function(E) ask<E>() => (env) => 'hello';

String Function(E) Function(String Function(E)) map<E>(

String Function(String) f,
) =>
(r) => (env) => f(r(env));

void main() {
pipe2(
ask<int>(),
map<int>((b) => 'world'),
);
}

The error is caused by the fact that map returns a generic function, but pipe expects a non-generic function.

Erik Ernst

unread,
Nov 14, 2023, 4:36:09 PM11/14/23
to Dart Misc, Francesco Gaudenzi, Dart Misc, Modestas Valauskas
Looks like a bug in the analyzer: The common front end (CFE) does succeed in performing the generic function instantiation (turning `map((b) => 'world')` into `map((b) => 'world')<int>`), and this transformation should be applied in the case where the expression (here: an actual argument to `pipe2`) is a generic function, and the context type is a non-generic function.
Reply all
Reply to author
Forward
0 new messages