dart-lang / dart-lang/language
An inferred actual type argument should be a denotable type
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
This issue proposes a _principle_ which we could use as a guideline when decisions must be made about how to perform type inference. I believe that we are mostly following this guideline already, but see below for some 'interesting' cases.
**Principle:** If type inference transforms an expression _e1_ by adding some actual type arguments _T1 .. Tk_, yielding the expression _e2_, then it must be true for each _j_ in 1 .. _k_ that there exists syntax which denotes _Tj_ in the given scope.
The point is that it would be useful to have a clear limit on the expressive power of type inference of the form "you could have written it yourself", which helps keeping programs comprehensible, and helps us avoid situations where we have to say to developers "you must convince type inference to give you that type argument, because there is no way you could write it explicitly".
A similar principle would apply to the type of a variable when it is inferred, and the type of a formal parameter (of a function literal based on the context type, or of an instance member declaration which is receiving some type annotations in its signature from type inference, based on declarations that it overrides):
**Principle:** The type of a variable or parameter and the return type of a function must be denotable in the given scope, also when it is inferred.
We have some immediate issues:
- Generic function instantiation will pass type arguments to a generic function _F_, thus yielding a non-generic function whose type is a generic instantiation of the type of _F_. Problem: We do not have the syntactic support to pass those type arguments at all (`f` is not an expression). Solution: We might be able to add support for this syntax, and we have already considered that for a while; so this issue may not be so serious.
- But privacy may be harder. Consider the following example, which shows that we already support giving a variable an inferred type that we cannot denote:
```dart
// Library lib.dart.
class A { foo() { print("foo"); } }
class _B extends A { bar() { print("bar"); } }
_B f() => _B();
// Library main.
import 'lib.dart';
main() {
var x = f();
x.foo();
x.bar();
}
```
- The same treatment is given in a case where the type is reified, namely for function literal parameters:
```dart
// Library lib.dart.
class A { foo() { print("foo"); } }
class _B extends A { bar() { print("bar"); } }
void f(void Function(_B) g) {
print(g.runtimeType); // Shows parameter type `_B`.
g(_B());
}
// Library main.
import 'lib.dart';
main() {
f((b) { b.foo(); b.bar(); });
}
```
- Considering an example like https://github.com/dart-lang/sdk/issues/36096, we probably want to specify that inference can pass a type argument which is not statically safe (example: passing `num` as the type argument to `printT` in `t2.printT()`, even though the bound is only known to be some subtype of `num`). So this is probably a non-problem, because we will pass `num` or make it an error, and those solutions will satisfy the principle that I'm proposing here. The derived principle (a special case of the main principle proposed here) would be that it is allowed for inference to provide certain type arguments which are not statically safe, namely in the case where the actual bound cannot be denoted, and neither can any of its useful (non-`Null`) subtypes. (Developers would, presumably, be able to add invariance to various parts of their typing, such that these situations do not occur any more, or at least only rarely.)
We might end up deciding that the principles must be weakened to allow for private types; or we might decide that the principles are useful, and the private types must be eliminated as inference results (possibly with a long transition period where they are only frowned upon, or possibly by approximating the private types from from above or below by public types, etc).
Contributor guide
Research direction
The issue names no implementation files or tests; start with the denotable-type principle and the private-type examples in lib.dart and main. Review the generic instantiation, inferred parameter, and issue 36096 cases, then establish whether the principle should be enforced, weakened, or approximated and what a resolved rule covers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100