dart-lang/site-www

Update noSuchMethod explanation for Dart 2

Closed this issue · 9 comments

from https://twitter.com/MatanLurey/status/933378071947702272

Sounds like @proxy will go away in Dart 2.

Informal language specification:
https://github.com/dart-lang/sdk/blob/master/docs/language/informal/nosuchmethod-forwarding.md

tl;dr, you can only call methods that aren't implemented if:

  1. The caller is of type dynamic
  2. The caller's super-types have that method that defined (even abstractly)

Concretely, the following is no longer valid in Dart 2:

@proxy
class A {
  noSuchMethod(_) => 'Hello';
}

main() {
  // Static analysis error, will refuse to build.
  print(new A().sayHello());
}

/cc @eernstg if you have specific additional questions.

Thanks for the details.

Just FYI, this had been marked as "update-for-dart-2" in

// update-for-dart-2
// Dart 2.0: hint • 'proxy' is deprecated and shouldn't be used

@eernstg - will @proxy be deprecated or completely removed in Dart 2? In either case, could you add a note to the noSuchMethod spec to clarify this point? (The noSuchMethod spec seems like a good place to add such a note -- unless this is (in)formally mentioned in another Dart 2 spec.)

@matanlurey - in #442 (comment), by "caller" I think you meant "callee"/"receiver", right?

After reading the informal spec, I didn't come across any restriction of the likings of point (1.) that you make in #442 (comment). I.e., a callee can be of any type.

In Dart 1, @proxy, applied to a class C, allowed developers to effectively turn off static checks for member access expressions over C. With @proxy gone, a developer is left with either ensuring that member access is statically valid (as determined by the usual Dart transitive (super-)interface lookup), or declaring the receiver to be of type dynamic (which effectively turns of static checks at the declaration level vs. the @proxy class level).

This issue was opened so that we could add a "Dart 2 difference" note to the Language Tour stating that @proxy will be deprecated and/or dropped in Dart 2. IMHO, that is the only note we need to add. The remaining differences, if any, in the associated semantics of noSuchMethod are either: (1) a natural consequence of the semantics of Dart 2 and the absence of @proxy, or (2) subtle differences in noSuchMethod semantics that don't belong in the Language Tour.

@eernstg: I believe that the following would still be valid in Dart 2; i.e., it would run and print 1, and the analyzer would report no errors or warnings. Is that correct?

class A { noSuchMethod(i) => 1; }
main() => print((new A() as dynamic).foo());

cf. dart-lang/sdk#31426


Edit: I just tested this under Dart 2.0.0-dev.17 + ddc and it seems to run fine.

As of commit 06f2a2e, proxy is not part of the Dart language specification. That same commit changes the SDK proxy declaration to be deprecated. So it's definitely justified to say that @proxy will go away in Dart 2, and no documentation should mention it as anything other than deprecated.

Given that dartLangSpec.tex has already been updated such that there is no proxy I think it's redundant to put it into nosuchmethod-forwarding.md (after all, the informal specs will all be integrated into dartLangSpec.tex and then become background material).

Reconsidering the example (but removing @proxy):

class A {
  noSuchMethod(_) => 'Hello';
}

main() {
  print(new A().sayHello()); // (*)
}

At (*), we will have a compile-time error because a member sayHello is accessed on a receiver of type A, and the interface of that class does not have a member of that name. So that's just a plain "type error" that has nothing to do with noSuchMethod.

The novelty of this is that we have no way in Dart 2 to say statically that "this class has all members" (which is what @proxy used to do in Dart 1).

However, that was a very local privilege anyway, because even such a magic object with all members would not satisfy any typing requirements beyond the ones declared in its superinterfaces (that is, roughly, types reachable through extends, with, and implements). So even though class A in (Dart 1 and with @proxy) "had" a sayHello method with no arguments and return type dynamic, you couldn't assign an instance of type A to a variable of type B (assuming something like abstract class B { sayHello(); }): the assignment would fail in checked mode (which would in practice be unacceptable). So "@proxy didn't work anyway, and now it's gone".

The remaining differences, if any, in the associated semantics of
noSuchMethod are either: (1) a natural consequence of the semantics of
Dart 2 and the absence of @proxy, or (2) subtle differences in noSuchMethod
semantics that don't belong in the Language Tour.

Agreed!

I believe that the following would still be valid in Dart 2; i.e., it would run and
print 1, and the analyzer would report no errors or warnings. Is that correct?

class A { noSuchMethod(i) => 1; }
main() => print((new A() as dynamic).foo());

Yes. So we will be able to consider objects with a non-trivial noSuchMethod as "having all members dynamically" (well, all members that noSuchMethod cares to emulate, anyway), and that was always true. But (assuming checked mode for Dart 1, or plain Dart 2 which has no modes) it was also always true that we can only pass objects around in typed code according to their declared supertype relationships. Removing @proxy helps to express these truths in a more consistent manner.

Haha! I just commented a couple of minutes ago, because I discovered that the first wording I proposed claims that we can call an unimplemented method m even in some cases where there is an implementation of m. You can take a look and fix it later, if you think it's sufficiently disturbing to justify another CL. ;-)

Yeah, I should probably fix that. :)