Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support inferring the return value of functions #4280

Open
rrousselGit opened this issue Mar 3, 2025 · 6 comments
Open

Support inferring the return value of functions #4280

rrousselGit opened this issue Mar 3, 2025 · 6 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@rrousselGit
Copy link

With records, it becomes tempting to rely more on type-inference. Inference is important for records, because it avoids lots of repetition

The problem is, inference for the return value of function is only available with closures:

// Inferred as ({String name, int age}) Function()
final example = () => (name: '', age: 42);

If we use a "normal" function, we are forced to specify the return value:

({String name, int age}) example() => (name: '', age: 42);

Proposal

It'd be cool to have a mean to define classical functions, but infer the value. One possibility could be:

infer example() => (name: '', age: 42);
@rrousselGit rrousselGit added the feature Proposed language feature that solves one or more problems label Mar 3, 2025
@mateusfccp
Copy link
Contributor

We could use _, as we already have it special-cased for wildcards, and AFAIK wouldn't cause any syntax issue.

Its also something used in other languages, like Kotlin (from documentation):

fun main() {
    // T is inferred as String because SomeImplementation derives from SomeClass<String>
    val s = Runner.run<SomeImplementation, _>()
    assert(s == "Test")

    // T is inferred as Int because OtherImplementation derives from SomeClass<Int>
    val n = Runner.run<OtherImplementation, _>()
    assert(n == 42)
}

So, in Dart, it would be:

_ example() => (name: '', age: 42);

@rrousselGit
Copy link
Author

I don't mind.

I proposed infer because I think that's the keyword that's going to be used for extracting generics:

class Bloc<BlocT extends Bloc<infer StateT>> {}

@lrhn
Copy link
Member

lrhn commented Mar 3, 2025

You're not forced to write the return type everywhere.

void main() {
  example() => (name: '', age: 42);
  print([example].runtimeType); // prints: List<() => ({String name, int age})>
}

This shows that the return type of the example function was inferred.

There is a difference between top-level inference and local inference.
The top-level inference is used for top-level and class-level declarations. The distinction exists because those declarations define the types and members that code inside functions uses, and because top-level declarations can be mutually recursive.

Take:

class Foo {
  Foo? next;
  foo() => switch (next) { null => "42", var next? => next.foo() };
}

To infer the return type of foo, the inference needs the return type of foo.
You can obviously find a solution (the least, viable solution is String), but that requires a fixed-point computation,
effectively solving X = UP(X, 'String'), which has any supertype of String` as solution.
If you have multiple classes and members that all refer to each other, it can get complicated.

Rather than trying to design a complete constraint gathering and solving system, Dart just requires top-level declarations to be typed. Then the types and members are well-defined, and it can do better inside function bodies.

@rrousselGit
Copy link
Author

Take:

class Foo {
Foo? next;
foo() => switch (next) { null => "42", var next? => next.foo() };
}

I'd expect it to behave exactly like:

class Foo {
  Foo? next;

  late final foo = () =>  switch (next) { null => "42", var next? => next.foo() };
}

That is:
It fails to compile with top_level_cycle as error code.

The request is only about supporting whatever closures support.

@Wdestroier
Copy link

Wdestroier commented Mar 4, 2025

I get top-level inference with late final foo = () => ...; too. The logic to pick the return type must already exist.

Could we have a package-wide setting to enable return type inference? Because prefixing the function with late final, infer or _ looks bad. Then the fontWidth(String text) => text.length * fontScale; method can infer the num return type.

@RohitSaily
Copy link

I think the approach proposed by @Wdestroier is preferable. If we had an analyzer setting for analysis_options.yaml files such as

analyzer:
      language:
            strict-inference: true

then all inferences in the code base must resolve to a non-dynamic type, an error is reported if inference fails in which case the type should be explicitly annotated.

This is preferable because it does not require additional repetitive syntax that would likely appear across many declarations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

5 participants