language
language copied to clipboard
Support inferring the return value of functions
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);
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);
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>> {}
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.
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.
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.
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.
Good luck with that #3222 So the tales were true afterall, ~JavaScript~TypeScript will eat everything in the end.