-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Informal proposal for covariant overrides. #28176
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
Conversation
|
cc @eernstg |
| ; | ||
|
|
||
| simpleFormalParameter: | ||
| metadata "covariant"? finalConstVarOrType? identifier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned elsewhere, we probably don't want to allow omitting the type after "covariant", so maybe:
simpleFormalParameter:
metadata ("covariant" "final"? type | finalConstVarOrType?) identifier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If covariant is neither a keyword nor a built-in identifier, then it can also be used as a type name, so it makes sense that we would require a type to follow it in order to prevent ambiguity.
class covariant {
void m(covariant covariant c) {}
}
but what about the case of a function signature? Consider
class covariant {}
class Timer {
int time(covariant f()) {}
}
How should we interpret the covariant before the parameter f?
Also, as a minor nit, the way the production normalFormalParameter is written the covariant keyword comes before the metadata for the parameter. I assume that isn't the intent. (Technically, the spec makes the same mistake for static and/or external functions, but analyzer ignores that and expects the metadata to precede any keywords. I expect we should do the same here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was the reason that I want to require the type.
The function type is a problem, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking more closely at Lasse's suggestion, it isn't clear to me why we'd disallow the use of either const or var with covariant, or why final would require a type. If those aren't desired restrictions, how about:
simpleFormalParameter:
metadata ("covariant" finalConstVarOrType | finalConstVarOrType?) identifier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could work too. The only problem with omitting the type (and why people will probably not do it even if they can) is that it's not clear which type the parameter is covariant wrt. Still, it should work.
Also, the function type example above isn't a problem because that syntax isn't handler by simpleFormalParameter. It means that you can't write covariant in front of a function signature (or if you do, it's a type - but that's silly, so maybe we should just make it a built-in identifier as soon as possible, it's not going to break anybody anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've introduced a new functionFormalParameter to work as the variant of functionSignature which is intended to declare a formal parameter. That one needs to admit covariant on the parameter declaration as a whole, but other usages of functionSignature (e.g., for declaring a top-level function) should not. We can't just add covariant in front of a functionSignature in the rule for normalFormalParameter, because it would then put the metadata in an inconsistent location.
Next, I have made covariant a built-in identifier, which I believe is accepted at this point. So covariant cannot be a type, and hence we cannot have the function header void foo(covariant covariant covariant);.
This means that we do not need to disambiguate unconditionally when seeing covariant in the parser, which means that we do allow omitting the type (void foo(covariant x);) and using covariant as the name of the parameter (void foo(covariant); and void foo(bool covariant);).
As a consequence, we interpret int time(covariant f()) as having a covariant parameter named f whose type is dynamic Function().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, @bwilkerson, I overlooked that you already mentioned the metadata/"keyword" ordering issue. As mentioned, I do handle the issue for covariant in the proposal, but I created a new issue about the similar issues that arise with static and external.
|
|
||
| The `covariant` modifier can *only* be applied to a parameter declaration | ||
| on an instance method, instance setter, or instance operator, and it can be | ||
| applied to a mutable field declaration. Anywhere else is a static error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
field -> instance field
(I know, "field" usually means instance, but it hasn't been formally defined - the formal name is "instance variable").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now using non-final instance variable.
|
|
||
| The `covariant` modifier can *only* be applied to a parameter declaration | ||
| on an instance method, instance setter, or instance operator, and it can be | ||
| applied to a mutable field declaration. Anywhere else is a static error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds slightly weird:
X can only do foo or bar, and it can do baz.
It's technically correct, but how about:
The
covariantmodifier can only be applied to a formal parameter on an instance
method, instance setter, or instance operator, or to a non-final instance field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now using the usual spec style 'It is a compile-time error if ..'.
|
|
||
| #### Overriding | ||
|
|
||
| With this feature, type checking of an overriding instance method or operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Type checking" is very overloaded. I would expect it to be the checking of values against types.
Maybe "... the validity of the type of the overriding ..." (but not perfect either)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now completely different.
|
|
||
| For a regular (non-covariant) parameter, the override rule is is unchanged: | ||
| its type must be a supertype of the type declared for the same parameter in | ||
| each directly overridden declaration. This includes the typical case where the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove "directly". Since it's transitive, it's the same thing, but I think it's simpler without.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec defines 'overrides' to mean 'overrides directly' (cf. \ref{inheritanceAndOverriding}: 'Then
Of courses, the spec never needs to talk about 'indirectly overrides', because it suffices to talk about the relationship between a declaration and the declaration that it directly overrides for all current purposes. But that won't suffice for covariant parameter type checking.
| Widget widget = group; | ||
| ``` | ||
|
|
||
| ... is perfectly fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this shows - subtyping never had anything to do with member signatures. Either elaborate what the concern could be, or remove the section, because I can't figure out what point it's trying to make.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed from the informal spec part, which is what I'm currently working on. It is still present at the end of the document (I'll return to that soon).
| widget.addChild(new Widget()); | ||
| ``` | ||
|
|
||
| ... does not. Both will fail at run time, but the whole point of allowing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one that reports an error at compile time won't fail at runtime - since it can never run.
| is, *D* has not been overridden by any other method declaration in | ||
| `C`) can be computed as follows: Collect the set of all declarations | ||
| of `m` in direct and indirect supertypes of `C`. For the reified | ||
| return type, choose the greatest lower bound of all declared return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just use the actual return type. It must be a subtype of all other return types, and there is nothing special about return types that makes you need to look at super declarations.
It's only because the "original type" is different from the "desired type" and "original type" isn't written in the function declaration, that we need to look at super-declarations to find it for covariant parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I'll just use the declared return type!
|
|
||
| (Note that the computation of a "least upper bound" in Dart does not | ||
| in fact yield a *least* upper bound, but it does yield some *upper | ||
| bound*, which is sufficient to ensure that this safety property holds.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LUB/MAX we are discussing elsewhere has the property that it sometimes has to give up. In that case, we should probably use Object here (which is always an upper bound). For inference, we want something else that defaulting to Object, so it's not part of the "LUB" computation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also simply reject the covariant parameter which creates a situation where there is no "reasonable" dynamic type. (Or we could use guarded functions ;-)
| fail. | ||
|
|
||
| The word "covariant" may be esoteric, but if a developer knows the word or | ||
| looks it up the meaning of this word directly addresses the functionality that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long sentence. Could there be a comma before "the meaning"?
|
|
||
| So we need to understand the original type, the desired type, and when | ||
| this feature comes into play. To enable it on a method parameter, you | ||
| mark it with the contextual keyword `covariant`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does "contextual" keyword mean that covariant is neither a keyword nor a built-in identifier in the sense defined in the spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible, yes. That's the least breaking way to introduce it.
The alternative is to make it a built-in identifier like get and set, but I would prefer to avoid that, at least until 2.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've had widespread support for making it a built-in identifier today. We can take another fight or two about it, but my current work is based on that assumption.
| ; | ||
|
|
||
| simpleFormalParameter: | ||
| metadata "covariant"? finalConstVarOrType? identifier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If covariant is neither a keyword nor a built-in identifier, then it can also be used as a type name, so it makes sense that we would require a type to follow it in order to prevent ambiguity.
class covariant {
void m(covariant covariant c) {}
}
but what about the case of a function signature? Consider
class covariant {}
class Timer {
int time(covariant f()) {}
}
How should we interpret the covariant before the parameter f?
Also, as a minor nit, the way the production normalFormalParameter is written the covariant keyword comes before the metadata for the parameter. I assume that isn't the intent. (Technically, the spec makes the same mistake for static and/or external functions, but analyzer ignores that and expects the metadata to precede any keywords. I expect we should do the same here.)
|
Hey @munificent, can we get this landed? |
|
@mit-mit I just returned to work on this, I hope to have an update which is usable as an informal spec for implementors later today. |
|
I just pushed a version of covariant-override.md which includes an informal specification at the beginning. This involved extracting a substantial amount of material from the document as a whole, which means that there is a lot of near-redundant material (same topic is addressed in the informal spec and, more verbosely, in the rest of the document). I'll look into that tomorrow. |
…dden method declared in multiple supertypes
…f the declared types
This is the informal proposal for the in-progress covariant overrides feature (#27486).