Skip to content

Conversation

@munificent
Copy link
Member

This is the informal proposal for the in-progress covariant overrides feature (#27486).

@munificent munificent added this to the 1.50 milestone Dec 21, 2016
@munificent munificent added the area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). label Dec 21, 2016
@munificent
Copy link
Member Author

cc @eernstg

;

simpleFormalParameter:
metadata "covariant"? finalConstVarOrType? identifier
Copy link
Member

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

Copy link
Member

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.)

Copy link
Member

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.

Copy link
Member

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

Copy link
Member

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).

Copy link
Member

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().

Copy link
Member

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.
Copy link
Member

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").

Copy link
Member

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.
Copy link
Member

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 covariant modifier can only be applied to a formal parameter on an instance
method, instance setter, or instance operator, or to a non-final instance field.

Copy link
Member

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
Copy link
Member

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)

Copy link
Member

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
Copy link
Member

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.

Copy link
Member

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 $m$ overrides $m^\prime$ if $m^\prime$ is not already overridden by a member of at least one of $S_1 \ldots S_{j-1}$'). This means that we can talk about 'overrides' and 'indirectly overrides'. But I thought that would be too subtle, so I made it a bit more explicit and used 'directly overrides' and 'indirectly overrides'.

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.
Copy link
Member

@lrhn lrhn Dec 21, 2016

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.

Copy link
Member

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
Copy link
Member

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
Copy link
Member

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.

Copy link
Member

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.)
Copy link
Member

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.

Copy link
Member

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
Copy link
Member

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`:
Copy link
Member

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?

Copy link
Member

@lrhn lrhn Jan 2, 2017

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.

Copy link
Member

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
Copy link
Member

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.)

@mit-mit
Copy link
Member

mit-mit commented Jan 4, 2017

Hey @munificent, can we get this landed?

@eernstg
Copy link
Member

eernstg commented Jan 5, 2017

@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.

@eernstg
Copy link
Member

eernstg commented Jan 5, 2017

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.

@eernstg eernstg merged commit 13f7163 into master Jan 19, 2017
@kevmoo kevmoo deleted the prop-covariant-overrides branch March 16, 2017 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). cla: yes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants