Skip to content
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

call method invocation and noSuchMethod forwarder #59952

Open
sgrekhov opened this issue Jan 22, 2025 · 4 comments
Open

call method invocation and noSuchMethod forwarder #59952

sgrekhov opened this issue Jan 22, 2025 · 4 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@sgrekhov
Copy link
Contributor

sgrekhov commented Jan 22, 2025

This issue derieved from the test mentioned in #55803 (comment). I didn't find a separate github item for this issue. Feel free to close this one as a duplicate if there is such one.

class BNSMC {
  // Should be NSM-forwarder.
  int Function(int) get call;

  Object? noSuchMethod(Invocation i) {
    if (i.memberName == #call) {
      print("isMethod=${i.isMethod}"); // false
      print("isGetter=${i.isGetter}"); // true
      return (int x) => x;
    }
    return super.noSuchMethod(i);
  }
}

void main() {
  (BNSMC() as dynamic)(42);
}

Here we have an attempt to invoke a call method, not a getter, so isMethod and isGetter should have opposite values.
cc @lrhn @eernstg to confirm.

@dart-github-bot
Copy link
Collaborator

Summary: noSuchMethod incorrectly identifies a call method invocation as a getter, resulting in isMethod being false and isGetter being true. This contradicts expected behavior.

@dart-github-bot dart-github-bot added area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Jan 22, 2025
@mraleph mraleph removed the triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. label Jan 25, 2025
@lrhn
Copy link
Member

lrhn commented Jan 26, 2025

Not sure if we have specified this particular interaction between dynamic and noSuchMethod. (But Erik will know ).

If the call should not invoke the call getter, what should it do?
It can either throw, or invoke noSuchMethod with an Invocation, which should have member name #call and be a method invocation. That's the invocation that failed to happen.

It'll be the only place where you can get an Invocation with the face name as a getter. Any other attempt to invoke a getter as a method would attempt to .call the getter value.
It might be surprising to some code that you can get a failed invocation with the same name as a getter.

@eernstg
Copy link
Member

eernstg commented Jan 27, 2025

It is a tricky case! ;-)

I think the actual behavior of the CFE matches the specified behavior:

We're considering an invocation of the expression e of the form (BNSMC() as dynamic), which means that we're applying the following rules:

  • im.isMethod evaluates to true.
  • ...

We're applying these rules because the previous cases do not match the given situation. The static analysis goes through the following steps: e is not a type literal, it is not an identifier, it is not a property extraction, and the static type of e is not an interface type that has a method named call. Hence, the invocation is not a compile-time error, and it is not "treated as" any other expression. At run time, the value of e is not a function object, and it doesn't have a method whose name is call.

(It doesn't help that it has a getter named call, we're only looking for a method.)

Following the rules at that point, we'll then invoke noSuchMethod with an Invocation whose isMethod is true.

There is another part of the language specification which is concerned with the behavior of the call getter, namely this location. However, these rules make no difference in this case because we're never invoking the getter named call.

It could be claimed that it is underspecified which value isGetter will have in the dynamic invocation. However, the only reasonable choice, I think, would be to follow the rule specified for the noSuchMethod forwarders. This means that isGetter will be false. Similarly, isSetter should be false because the given member name isn't a setter name (it doesn't end in =).

It might be surprising to some code that you can get a failed invocation with the same name as a getter.

Yes, I was thinking about that, too. However, it's probably the most consistent behavior, measured against the behavior of statically checked invocations.

@eernstg
Copy link
Member

eernstg commented Jan 28, 2025

Turns out that DartPad (DDC) yields isMethod=true, but all other backends yield isMethod=false. So those other backends should be adjusted.

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). type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

5 participants