-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Inconsistent runtime errors for implicit method invocations. #36420
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
[Edit] April 5th 2019, I got two things wrong below:
Next, invocation of a value of type [Original text] Did you mean Other than that, it is specified that a dynamic invocation of an object whose dynamic type is not a subtype of So The behavior that I can see would not be a bug in the front end, but if DDC throws, we might have a DDC bug. |
The test in question is language_2/call_method_must_not_be_field_test.dart. It has this comment:
The test asserts that |
Mark is looking at two tests failing DDK but apparently passing DDC and Dart2JS: language_2/call_method_must_not_be_field_test/06 @eernstg @jmesserly - can you sanity check that the test is valid? |
The test looks right to me.
Yeah, the comment is unclear, sorry about that. What this comment is trying to say:
Is that a field named In all cases, dynamic dispatch and compile time give the same answer. Either it's not allowed (the first group of tests), or it is allowed (the second group). That's why Another way of saying this: if you rename "call" to "foo" in that test, all of the tests should behave the same. A field with the name "call" is not special. If that helps. I'll try to find the spec. |
It's in section 16.17.5, "Function Expression Invocation" which has the rules for "call" methods (there is no corresponding rule for "call" fields/getters that happen to return a function). |
@Markzipan wrote:
The test is correct here. I mistakenly thought that @jmesserly wrote:
Agreed. Sorry about misreading |
Ok, so the question at hand is code that looks like: dynamic d = ...
d(); If If I believe that we have currently specified the second behavior. DDC implements the second behavior, and the front end based tools currently implement the former behavior. We have a very short window (basically until DDC goes away in favor of DDK) to make this change in a mostly non-breaking way, if we wish to fix it to work as specified (and as implemented in DDC). Alternatively, we could just specify this to work as implemented in the CFE based tools. For the language team, how much do we care about this distinction? For the FE/VM/DDC/dart2js folks, how hard would this be to fix? I'm guessing that the current dart2js behavior is better for the dart2js calling conventions? cc @kallentu @sigmundch @rakudrama @mraleph @mkustermann @a-siva @johnniwinther @vsmenon |
What if it's a class with a call getter that returns a class with a call getter? Consider: int depth = 0;
class C {
void Function() get call {
if (depth > 100) return () {};
print(depth++);
return this;
}
}
main() {
dynamic d = C();
d();
} Would this program be expected to count to 100? |
With minor edits for typing, that is, in fact, what it currently does on all platforms except DDC. |
@leafpetersen wrote:
(which was: with
Regarding:
When I was working on this particular text I made sure to maintain the property that there can be a more primitive notion of function invocation than that of invoking an instance method on an object. This makes it possible to use a faster protocol for invocation: An instance method invocation will in general need a dispatch step (a cheap and well-known one would be a vtable lookup), but an invocation of a function object could rely on having the jump target stored directly in the object. We also made sure that no value whose static type is a function type can be an instance of a user-written class, so every statically checked function invocation could use such a fast invocation mechanism. I think it is useful to allow for such a primitive notion of calling. However, I don't think the ability to look up a getter during a dynamic invocation and proceed recursively on the returned result will affect the ability to have these primitive invocations. We basically omitted getters because the existing specification insisted on finding a method, and it seemed convoluted and not-so-useful to generalize the semantics of function invocation to look for a getter and repeat as well. |
The work needed to either support or disallow the invocation on the getter is in the backends. From a static perspective there is no information available about the potential target so CFE cannot handle the case. |
My vote is to update the spec: it's the path of least resistance (it already works almost everywhere) and it moves the language to be more consistent state (making Otherwise, the main challenge in dart2js (and I expect the same is true in other backends), is to track the distinction of |
I vote to change the spec like @sigmundch I don't think changing VM implementation would face any engineering challenges, but I don't think we should spend time on this given that all non-DDC implementations are aligned and there are no strong reasons for specified behaviour. |
We deliberately made Another, potentially more breaking, approach is to entirely disallow dynamic invocation of non-functions. It would still require backends to change behavior, but towards a simpler |
I suppose one option is just to allow this.
Is there really any user value in this? |
I created the PR dart-lang/language#594 in order to clarify the changes needed in the specification, in order to allow for a So, @lrhn, do you wish to maintain that we shouldn't do this? |
Is that enough? I did not see anything regarding implicit conversion from callable object to Function, likely because it's part of inference, which is also not specified. We currently say that That is a single Example: abstract class C<T> {
T get call;
}
...
C x = ...;
Function f = x; If What if class D implements C<D> {
D get call => this;
}
C x = D(); Then we are back at an infinite recursion of getting I really, really wanted to get rid of that potential infinite recursion. It's not just in untyped code, as this example shows, and I also don't want dynamic invocations to be more powerful than typed invocations (can call a It does mean that a dynamic invocation So yes, I still want to maintain that we should not treat getters as |
If we do this at all, I would propose that no, we simply say that if the result type of the getter is a subtype of function, that's what you get, and otherwise it's a static error. But I don't think I have any problem with just saying it's a static error. If For the
as syntactic sugar for
I think this at least eliminates the infinite recursion. |
In order to resolve this, we can decide that the current specification is what we want (and drop dart-lang/language#594), which means that Implementation Effort: Getter is ErrorHere we'd drop dart-lang/language#594. In order to disallow the getter invocation, we'd need the following implementation effort: Both class A {
Function get call => () { print("A.call"); };
}
main() => (A() as dynamic)(); So For an invocation which is checked statically we have this example: class A {
Function get call => () { print("A.call"); };
}
main() => A()(); This is accepted by the analyzer; it would then have to change such that this example is a compile-time error. The common front end raises an error in this case (and requires a method), so there are no further changes. Implementation Effort: Getter is AcceptedHere, we'd land dart-lang/language#594. The common front end would need to change to not raise a compile-time error for the statically checked case. Code generation might handle this case already, otherwise the generated code must be changed such that a getter invocation will take place. No changes are needed in the analyzer. |
@leafpetersen, @lrhn, you seem to prefer making the getter invocation an error? @munificent, you also put the focus on the (perhaps excessive) expressive power of repeated getter invocations, wdyt? |
The following test snippet passes (throws) in DDC/analyzer but doesn't emit an error in the VM:
Ditto for when
call
is a getter. From what I could find, CFE doesn't provide the backends a way to disambiguate the two cases.The text was updated successfully, but these errors were encountered: