-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Infer generics when doing pattern matching #55602
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
Comments
My immediate thought is that the pattern should infer Either a bug, or I'm forgetting something. Have to do a thorough reread of the pattern inference rules to be sure. (A quick reread does look like it says what I remembered it as saying: For an object pattern, do downwards inference on the type using the matched value type as context type, to infer type arguments if applicable. Then look up the member getters on that type to give the matched value type for the property patterns. So probably an implementation bug that should be moved to the SDK repo.) |
I cannot reproduce this. In both the analyzer and the VM the static type of class C<T> {
final T value;
C(this.value);
}
Type typeOf<T>(T value) => T;
void main() {
var x = switch (C<int>(1)) {
C(:final value) => typeOf(value),
};
print(x);
} Do you have a more complete reproduction that demonstrates this? |
Ah indeed this happens in some scenarios. The case where I've faced this is with AysncValue from Riverpod. Here's an extracted version: void main() {
switch (AsyncData<int?>(42)) {
case AsyncValue(:final value?):
print(value);
}
}
abstract class AsyncValue<T> {
T? get value;
}
class AsyncData<T> extends AsyncValue<T> {
AsyncData(this.value);
@override
final T value;
} |
That does give The static type of The The most likely cause of the result is that the pattern infers All the specification says is "perform downwards inference" trying to match So, either a bug, or the specification is unclear. |
Just a bump that I've also been affected by this. |
Either bug in specification or implementation. We'll have to decide. |
The matching operation If we've trying to compute the constraints on It's a bit like this (which won't be completed anyway because it's a compile-time error): void f<X>(List<X> list) {}
void main() {
Iterable<int> xs = [];
f(xs); // `f<int>` yields a compile-time error.
} I think the crucial point here is that the matched value type is of the form So perhaps the algorithm where actual type arguments are computed for an object pattern should have an extra step: If the object pattern has a class Alternatively, we could just warn the developer that the desired subtype relationship can't happen, and hence they don't actually want to write |
Another example, but not for subclasses of the current instance, but for subclasses of the generic argument: @immutable
sealed class EntitySelectorState<T extends BaseEntity> {
const EntitySelectorState({
this.lastDocumentId,
this.currentQuery = '',
this.isLoadingMore = false,
});
final String? lastDocumentId;
final String currentQuery;
final bool isLoadingMore;
// A homomorphism on the discriminated union of the selector state
K map<K>(
K Function(InitialEntitySelectorState<T>) onInitial,
K Function(LoadingEntitySelectorState<T>) onLoading,
K Function(SuccessEntitySelectorState<T>) onSuccess,
K Function(FailureEntitySelectorState<T>) onFailure,
) {
return switch (this) {
InitialEntitySelectorState<T> state => onInitial(state),
LoadingEntitySelectorState<T> state => onLoading(state),
SuccessEntitySelectorState<T> state => onSuccess(state),
FailureEntitySelectorState<T> state => onFailure(state),
// I want to remove these:
InitialEntitySelectorState<BaseEntity>() => throw AssertionError('Unexpected (initial) state: $this'),
LoadingEntitySelectorState<BaseEntity>() => throw AssertionError('Unexpected (loading) state: $this'),
SuccessEntitySelectorState<BaseEntity>() => throw AssertionError('Unexpected (success) state: $this'),
FailureEntitySelectorState<BaseEntity>() => throw AssertionError('Unexpected (failure) state: $this'),
};
}
} Here, we know T as constant throughout the class / method declaration. |
It seems likely that you would want to restrict the type argument of // Glue code.
const immutable = 1;
class BaseEntity<T> {}
abstract class InitialEntitySelectorState<T extends BaseEntity>
implements EntitySelectorState<T> {}
abstract class LoadingEntitySelectorState<T extends BaseEntity>
implements EntitySelectorState<T> {}
abstract class SuccessEntitySelectorState<T extends BaseEntity>
implements EntitySelectorState<T> {}
abstract class FailureEntitySelectorState<T extends BaseEntity>
implements EntitySelectorState<T> {}
// Original example.
@immutable
sealed class EntitySelectorState<T extends BaseEntity> {
const EntitySelectorState({
this.lastDocumentId,
this.currentQuery = '',
this.isLoadingMore = false,
});
final String? lastDocumentId;
final String currentQuery;
final bool isLoadingMore;
// A homomorphism on the discriminated union of the selector state
K map<K>(
K Function(InitialEntitySelectorState<T>) onInitial,
K Function(LoadingEntitySelectorState<T>) onLoading,
K Function(SuccessEntitySelectorState<T>) onSuccess,
K Function(FailureEntitySelectorState<T>) onFailure,
) {
return switch (this) {
InitialEntitySelectorState<T> state => onInitial(state),
LoadingEntitySelectorState<T> state => onLoading(state),
SuccessEntitySelectorState<T> state => onSuccess(state),
FailureEntitySelectorState<T> state => onFailure(state),
};
}
} |
Thank you; I discovered that the constraint caused a default inferral in the base classes;
which implicitly made it In TypeScript, this would not have happened (it would error on the subclass declarations), because it would have required this declaration: |
Yes, we discussed having the notion of default values of type parameters, but instead we're using a process which is called instantiation to bound. Support for defaults could be added in the future (see dart-lang/language#283). In the case (the rather common case, I think) where explicitly provided type arguments are required as a matter of style, you can enable I'm afraid that |
Hello!
It appears that currently, when we write:
then
value
is inferred as "dynamic". But in this context, T is guaranteed to be at leastint
Would it be possible to consider
case Generic(<...>)
as caseGeneric<int>(<...>)
?The text was updated successfully, but these errors were encountered: