-
Notifications
You must be signed in to change notification settings - Fork 200
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
Non-spec compliant behavior of dynamic dispatch on Object methods with unusual arguments #3895
Comments
I believe we decided at some point, possibly around Dart 2, that dynamic invocations of " You could still call with seemingly invalid arguments, because they might be supported, or they might hit That would prevent some possible operations, fx There should be issues discussing this. |
Thanks, @lrhn. After a bunch of digging I found dart-lang/sdk#32414, which I assume is the issue you're referring to. |
That does sound like the discussion I remember. Well found! |
I'm quite opposed to changing this back to the "just use dynamic always" behavior - I think doing so would be a real regression in user facing behavior. I'm ok keeping the "just use dynamic if the argument list is weird" behavior, though I continue to find it pointless. :). This section is also relevant to this discussion. |
We do have rules that correspond to the implemented behavior, in the section 'Type dynamic' in the language specification. So we could just keep the currently implemented behavior and section 'Type dynamic' in the specification unchanged, and all is good... ... except that the language specification needs to be modified slightly: In section 17.15.3 'Binding Actuals to Formals' at the location where member accesses on a receiver whose type is dynamic bounded is specified, we should add a reference to the section 'Type dynamic' saying that the general rule doesn't apply when the function invocation is an ordinary member invocation whose name is one of the |
@stereotype441, I was just about to write a PR to add those missing references in the language specification. If you want to do it then just give me a heads up. |
No, go right ahead. I'll be happy to review it. |
Very good! I'll do it tomorrow, CEST. |
It's not there because it's awesome, but because it's sound. The way dynamic invocations work, in the presence of That means we can soundly use the declared return type from If the argument list shape matches the parameter list from the If the argument list shape doesn't match, then the call may invoke |
@lrhn I think you've reverse the interpretation of what I said in the quoted bit? You just gave the justification for why we use the declared |
The alternatives would be:
Each has trade offs. I don't think any of the choices are inherently superior, or more pointless than the other. |
Bingo! :). Yes, I understand that if we choose not to accept calls to |
Do we allow this because a runtime type check is too expensive? |
Showing an error for |
For what it's worth, I wrote this code in the SDK long before we started this discussion: /// Returns a string representation of this type.
///
/// If [parenthesizeIfComplex] is `true`, then the result will be surrounded
/// by parenthesis if it takes any of the following forms:
/// - A type with a trailing `?` or `*`
/// - A function type (e.g. `void Function()`)
/// - A promoted type variable type (e.g. `T&int`)
@override
String toString({bool parenthesizeIfComplex = false}) =>
switch (nullabilitySuffix) {
NullabilitySuffix.question => _parenthesizeIf(
parenthesizeIfComplex,
'${_toStringWithoutSuffix(parenthesizeIfComplex: true)}'
'?'),
NullabilitySuffix.star => _parenthesizeIf(
parenthesizeIfComplex,
'${_toStringWithoutSuffix(parenthesizeIfComplex: true)}'
'*'),
NullabilitySuffix.none =>
_toStringWithoutSuffix(parenthesizeIfComplex: parenthesizeIfComplex),
}; Not sure if you count the brainchild of someone like me to be "meaningfully different from 0" though 😃 Also relevant: I don't believe I ever invoke this method dynamically. |
I found 6.8k files where people use optional named parameters in toString and 968 files where people use optional positional parameters in toString on GitHub. toString with optional named parameters: https://github.com/search?q=toString%28%7B+language%3ADart+&type=code From the DX point of view, the last alternative can be great imo. Always show the error at compile time if it's known to always be an error at runtime (if the code might work, then don't show an error). And infer the known type if any (toString can only return a String effectively) or else dynamic. |
But do they call those methods dynamically? (Statistically ... probably some of them.)
We allow it beacuse we have never disallowed it. There is no difference (in the language semantics) between calling We could change that, and say that if You can still try to call the method with more arguments than the interface allows, but if that fails, it'll always throw. Since any object implementing an interface with a Or, if we are willing to change behavior of That is: A dynamic invocation of |
Yeah, I should have qualified my statement with this. It's not terribly surprising that someone would add an optional parameter to In any case, I'm not proposing that we remove support for this, just opposing removing the existing support for statically checked dynamic calls to the object methods . At a meta-level, I think that a big factor in this is the fact that even today it's very easy to unexpectedly fall into to dynamic code in Dart. If it was essentially impossible to accidentally end up with unexpected dynamic calls, I'd be more open to not special casing dynamic calls to the object methods. |
Thanks for the discussion, everyone! I now understand that:
Although there's some discussion here about possibly changing the behavior, it reads to me as largely speculative, and I think if anyone wants to pursue it, it would be better addressed as a separate proposal anyhow. (For example, @lrhn's comment that |
While writing up a detailed specification for type inference, I noticed a difference between the behavior of the analyzer and CFE and the current text in
specification/dartLangSpec.tex
.In the "Method Invocation -> Ordinary Invocation" section of the spec, in the case explaining how to interpret a method invocation i of the form e.m<...>(...) (where <...> is understood to be optional), there is this text:
This seems to pretty clearly imply that the following method invocations should all be accepted, and have static type dynamic (assuming that the type of
d
is dynamic bounded):d.toString()
d.toString(0)
d.toString(x: 0)
(Note that all of these invocations could be semantically meaningful, since it is legal for an override of
toString
to accept optional arguments.)It also seems to imply that the following method invocations should all be accepted at compile time and have static type dynamic (again assuming that the type of
d
is dynamic bounded), even though they are guaranteed to fail at runtime regardless of the value bound tod
(since a non-generic function can't be overridden by a generic function):d.toString<int>()
d.noSuchMethod<int>(d)
d.toString<int>(0)
d.toString<int>(x: 0)
d.noSuchMethod<int>()
d.noSuchMethod<int>(d, d)
d.noSuchMethod<int>(d, x: d)
But the actual behavior of the analyzer and the CFE is that:
d.toString()
has static type String,d.toString(0)
andd.toString(x: 0)
have static type dynamic,d.toString<int>()
andd.noSuchMethod<int>(d)
are rejected with a compile-time error, andd.toString<int>(0)
,d.toString<int>(x: 0)
,d.noSuchMethod<int>()
,d.noSuchMethod<int>(d, d)
, andd.noSuchMethod(d, x: d)
) have no compile-time error, and have static type dynamic.Although we have specified a few rare circumstances in which the static type of an invocation depends on the type of invocation arguments (see number-operation-typing.md), this is the only situation I know of where the static type of an invocation depends on the presence or absence of invocation arguments. And it's the only situation I know of where the presence or absence of invocation arguments affects whether type arguments are accepted.
This all feels like an unnecessary source of complexity in the type inference logic. My preference would be to change the implementations to comply with the existing spec here, and accept all of these forms without complaint at compile time (and make them all have static type dynamic).
However, I could see two possible counter-arguments to that:
d.toString()
has static type dynamic rather than String, since it might lead to more dynamism and less static error checking in code that follows.It's possible that I could be persuaded to accept, as an alternative, that:
d.toString(...)
always has static typeString
d.toString<...>(...)
is always a compile-time errord.noSuchMethod<...>(...)
is always a compile-time errorNote that either of these possibilities would technically be a breaking change, so they would have to be done using the breaking change process or using language versioning (I would prefer to use the breaking change process, since I believe code that is affected is extremely rare).
@dart-lang/language-team what do you think?
The text was updated successfully, but these errors were encountered: