dart-lang / dart-lang/language

Implicitly create static methods as wrappers for instance methods

Open
#3,786 7 comments 6 reactions 0 assignees View on GitHub
feature
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

In Python, any instance method is really a static method with a `self` parameter, and the object you call the method on is implicitly passed first to this method. For example:
```python
class User:
def __init__(self, name): self.name = name
def say_hello(self): print(f"Hello, I am {self.name}. How are you?")

alice = User("Alice")
bob = User("Bob")
alice.say_hello()
User.say_hello(alice)

users = [alice, bob]
greetings = map(User.say_hello, users)
```

In Dart, we often like to use tear-offs instead of passing closures around. A useful example is the following:
```dart
class User {
static String makeGreeting(User user) => "Hello, I am ${user.name}. How are you?";

final String name;
User(this.name);
}

void main() {
final users = [User("Alice"), User("Bob")];
final greetings = users.map(User.makeGreeting);
}
```

However, it is no longer possible to pass a tear-off if we make the greeting an instance method:

```dart
class User {
final String name;
User(this.name);

String makeGreeting() => "Hello, I am $name. How are you?";
}

void main() {
final users = [User("Alice"), User("Bob")];
final greetings = users.map((user) => user.makeGreeting());
}
```

My feature request is to implicitly (or on-demand) make available a static method that's equivalent to the Python version of every instance method. Not advocating for instance methods to be _replaced_ by static methods. But, if there's an instance method named `A.b()`, then there should be a static method `A.$b(A a)`, and it should be possible to refer to it as just `A.b` as syntax sugar. In other words:

```dart
class User {
// implicitly generated
static String $makeGreeting(User user) => user.makeGreeting();

final String name;
User(this.name);

String makeGreeting() => "Hello, I am $name. How are you?";
}

void main() {
final users = [User("Alice"), User("Bob")];
// translated to User.$makeGreeting
final greetings = users.map(User.makeGreeting);
}
```

The semantics seem to be well-defined as far as I can tell. The alias of `User.makeGreeting` for `User.$makeGreeting` is safe because there cannot be a static member with the same name as an existing instance member, and using an instance member without an instance is always an error. In other words, the alias strictly adds functionality and doesn't break any existing code or create conflicts. Additionally, the compiler may only decide to generate these if they're directly referenced in the first place. This would allow us to use tear-offs way more frequently, especially in JSON contexts where `(obj) => obj.toJson()` is quite common.

**Edit**: One specific case has been pointed out to me where this could cause a conflict:
```dart
class ClassName {
void methodName() { }
}

ClassName generateInstance() => ClassName();

void main() {
final instance = generateInstance();
final ClassName = instance;
ClassName.methodName(); // refers to the instance method
}
```

In cases like these, where `ClassName.methodName` is already a valid identifier and would thus cause a conflict, I would say to _not_ generate the implicit static wrapper, to avoid breaking existing code and causing "magic" behavior. But I'm open to whatever makes more sense on a case-by-case basis as well.

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the Python and Dart examples in the issue, then trace how Dart currently resolves instance tear-offs, static members, and identifiers that shadow class names. Done means producing an agreed language-design specification for implicit wrappers, including the stated conflict case and generation semantics.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.