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

Extension member is applicable when on-type has static member with the same basename #56818

Closed
sgrekhov opened this issue Sep 30, 2024 · 3 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

According to the Dart specification (13.2.2 Applicability of an Extension)

We say that E is applicable to e if the following conditions are all satisfied:
...

  • The type S does not have a member with the basename m, and S is neither dynamic nor Never.

It is not said here that the member should be an instance member. So, if an on-type has a static member with a basename m and extension whith instance member m then it is an error to invoke m as an instance. But the following code is accepted by both analyzer and VM and doesn't produce expected errors.

class C {
  static String get m1 => "m1";
  static String m2() => "m2";
  static void set m3(String _) {}
}

extension Ext on C {
  String get m1 => "Ext.m1";
  String m2() => "Ext.m2";
  void set m3(String _) {}
}

main() {
  print(C().m1); // prints Ext.m1
//          ^^
// [analyzer] unspecified
// [cfe] unspecified

  print(C().m2()); // prints Ext.m2
//          ^^
// [analyzer] unspecified
// [cfe] unspecified

  C().m3 = "";
//    ^^
// [analyzer] unspecified
// [cfe] unspecified
}

cc @eernstg

Dart SDK version: 3.6.0-277.0.dev (dev) (Tue Sep 24 21:06:37 2024 -0700) on "windows_x64"

@dart-github-bot
Copy link
Collaborator

Summary: The issue reports that Dart's extension member applicability rule is not correctly enforced. The code demonstrates that an extension member is accessible even when the on-type has a static member with the same basename, which contradicts the specification.

@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 Sep 30, 2024
@eernstg
Copy link
Member

eernstg commented Sep 30, 2024

Thanks for bringing this up, @sgrekhov! The specification does indeed make it an error.

It is a somewhat delicate choice: We generally consider an instance member to have a higher priority than an extension instance member:

class A {
  void foo() => print('A.foo');
}

extension E on A {
  void foo() => print('E.foo');
}

void main() {
  A().foo(); // No error, prints 'A.foo'.
}

We also ensure that instance members and static members cannot have name clashes (this is a very fundamental property: No scope can have multiple declarations with the same name):

class C {
  void foo() {}
  static void foo() {} // Error.
}

We even ensure that a static member can't shadow an inherited instance member:

class D {
  void foo() {}
}

class E extends /*or some other subtype relation, e.g., implements or with*/ D {
  static void foo() {} // Error.
}

It is always the static member which is flagged as an error, which could be taken to imply that the instance member is given a higher priority than the static member, even though that's not explicitly a normative property.

This means that we can't use the current rules to determine the most natural or consistent priorities of the static member vs. the extension instance member: In case of a conflict they both yield to an instance member.

When a conflict occurs between a static member and an extension instance member, it would be inconsistent to flag the extension instance member as a compile-time error: We don't even do that when the extension instance member conflicts with an instance member. It is also unthinkable to report the static member as a compile-time error: We never flag a class/mixin/... declaration as an error based on anything which is written in an extension declaration. So the consistent approach would be to accept the declarations without reporting any errors.

We must then consider invocations that might resolve to either of the parties in this kind of conflict.

Following the rule that "the lexical scope always wins", an invocation in the body of the on-type should resolve to the static member, and an invocation in the body of the extension should resolve to the extension instance member:

class F {
  static void foo() => print('static F.foo');
  void bar1() => foo(); // 'static F.foo'.
}

extension G on F {
  void foo() => print('G.foo');
  void bar2() => foo(); // 'G.foo'.
}

The remaining case is the qualified invocation of the given member name, that is, an expression of the form e.foo (which may be followed by additional selectors and/or occur as assignable expressions, e.g., e.foo().baz = 14).

In this situation, the expression is unambiguously resolving to the static member when e is a type literal that denotes the given static namespace (F, in the example above). Conversely, it cannot possibly be an invocation of the static member when e is an expression whose static type matches the on-type (here: F, or a subtype of F).

I've created a proposal to make this situation a non-error in dart-lang/language#4113.

@lrhn lrhn removed the triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. label Oct 1, 2024
@lrhn
Copy link
Member

lrhn commented Oct 1, 2024

I'm pretty sure that there was never any intent to prevent an extension member with the same name as a static member.
The phrasing is that "if the type S has a member", where S is the static type of an expression. You cannot access static members through a static type, and I would't count the type as having any static members at all. In this case, "type" means something closer to "interface", except that not all types are interface types, but they still have members.

I would say that the spec should be more precise, but that we haven't (so far) said that static members exist on types, and the implementations are absolutely correct in their interpretation.

(Enum member shorthands might muddle the waters more, if we are using a static context type as a guide to where to look for static members, but it should be possible to keep the concepts apart, like we do for accesses through type alias declarations.)

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

4 participants