-
Notifications
You must be signed in to change notification settings - Fork 201
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
Specify that constant expressions of type Type
and constant type expressions are canonicalized
#95
Comments
I'd be happy with this. A type that occurs verbatim in the source code is canonicalized, even when passed through type variables. I'm not sure it's viable. It assumes some kind of connection between types and Example: class C<T> {
Type get typeArgument => T;
C<List<T>> wrap() => C<List<T>>();
}
main() {
var c1 = C<List<Int>>();
var c2 = C<int>().wrap();
print(identical(c1.typeArgument, c2.typeArgument));
var c3 = C<dynamic>();
print(identical(c3.typeArgument, dynamic));
} There is no reason to believe that an implementation represents the type bound to |
Cf. discussion IRL with Lasse, it may be too expensive to do what I proposed (even though my proposal makes it OK for class C<T> {
Type get typeArgument => T;
C<List<T>> wrap() => C<List<T>>();
}
typedef T2 = List<int>; // Using new, generalized typedef.
main() {
print(identical(C<List<int>>().typeArgument, T2)); // Must print 'true'.
} That's because the type argument So we may need to be very careful about the performance implications and the implementation effort needed when we decide on exactly how much canonicalization we wish to require. Of course, it is always OK for an implementation to go further and canonicalize additional instances of |
An update on the status of this issue as of August 2022: The null safety feature specification now specifies a run-time equality relation on reified types (implemented by operator Moreover, two constant type literals are identical if they are equal according to the above equality. What remains to be resolved in this issue is canonicalization of reified types that are not constant expressions. In particular, Note that all examples in the comments above succeed today (the result is 'true' every time), which means that class E<X> {
Type get listX => List<X>;
}
main() {
print(identical(
E<int>().listX,
E<int>().listX,
));
} |
I'd be very wary about requiring canonicalization of run-time allocated values of any kind. That has a risk of memory leaks by keeping the canonical value alive for too long. Imagine us saying that I'd rather not promise anything, and let implementations do what is more efficient. |
Right, this kind of situation is exactly the reason why I said
You're basically arguing that canonicalization for reified types that are not obtained as the result of constant expression evaluation should be 100% implementation specific. That's definitely a possibility, and it's easy to specify, and we already have an implementation. ;-) I'm just noting that we could consider cases like this: class C<X> {
Type get theX => X;
}
Type typeOf<X>() => X;
typedef IntList = List<int>;
void main() {
void check(Type t) => print(identical(IntList, t));
check(List<int>); // True!
check(C<List<int>>().theX);
check(typeOf<List<int>>());
} In these cases there is a compile-time constant type So maybe there are some cases like this where we would actually want to decide that the result should be canonicalized, even though the reified type is not obtained by evaluating a constant expression, especially if this behavior is something that large programs (say, using AngularDart) rely on. |
In response to #92, about the use of
==
vs.identical
to detect that two instances of typeType
reify the same type, we specify thatType
instances that constantly reify a type are canonicalized.Similarly, we may perhaps wish to specify that
o.runtimeType
(whereruntimeType
has no overriding declaration, that is, it invokes the implementation inObject
) must return a canonicalized instance ofType
in the case whereo
was created by an instance creation expressione
(possible a constant object expression) as an instance of a non-generic class, or as an instance of a generic class where the type arguments passed ine
(explicitly, inferred, or obtained via instantiate-to-bound) are constant type expressions. It is not obvious to me whether this will be easy to achieve in terms of implementation effort, or whether it will work well, in terms of performance implications for the language as a whole.The next case seems more safe to require: We specify that whenever an actual type argument is a constant type expression, any evaluation of the corresponding type variable must return a canonicalized value. For instance, the following must print 'true' for every statement in
main
:Note that we do not require
identical(C<List<int>>().dOf().runtimeType, typeOf<D<List<int>>>())
, even though the reified type is in both casesD<List<int>>
, which is a constant type expression. This is because the given instance was created by evaluating an instance creation expression where the actual type argument was not a constant type expression (it wasX
), so that type argument is not statically known to be canonicalized.These rules would still not make the test using
identical
safe for any particular pair of instances ofType
, because it requires developers to follow certain conventions in order to make it known statically that both of those instances were originally obtained by evaluation of a constant expression, and that's not a decidable property for Dart programs in general. But it seems to make theidentical
type test safe in a set of situations that is somewhat less intractable for developers who are willing to go the extra mile in order to get this optimization.The text was updated successfully, but these errors were encountered: