Skip to content

Provide a way to fully close a type in the same library #3221

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

Closed
Reprevise opened this issue Jul 18, 2023 · 2 comments
Closed

Provide a way to fully close a type in the same library #3221

Reprevise opened this issue Jul 18, 2023 · 2 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@Reprevise
Copy link

Apologies if there is an issue similar to this, I tried looking for it but I might not have used the correct terminology.

Let's use this as an example:

class Foo {
  const Foo({this.a});

  final String? a;

  void run() {
    if (a == null) return;

    print(a.codeUnits); // a cannot be unconditionally accessed because the receiver can be null
  }
}

class Bar extends Foo {
  @override
  String? get a => Random().nextBool() ? null : "a";
}

The error above makes sense. To fix it, we need to add final a = this.a as the first line of the run() method and it works perfectly. However that can be annoying.

I'm requesting an additional class modifier be added to Dart, perhaps something like closed which prevents any class period from extending or implementing it. That way, the a field in the Foo class gets treated as a variable member (unknown if this is the correct term but I hope you know what I mean).

@Reprevise Reprevise added the feature Proposed language feature that solves one or more problems label Jul 18, 2023
@leafpetersen
Copy link
Member

I believe this is discussed here. Let me know if I've misunderstood your request.

@lrhn
Copy link
Member

lrhn commented Jul 19, 2023

We could decide to allow promotion of final fields that are definitely never overwritten, like we intend to do for final private fields.

The rules should be the same: only works inside the same library, and it should be decidable from looking only at that library that the field cannot be overridden.

The "only same library" restriction is because the API doesn't promise that the field won't be a getter in the next version of the package. We don't have a way to declare "stable getters". So being a focal variable is an implementation detail, and nobody but the author gets to rely on that. Because then they can fix any problems if they change it to a getter..

Making sure a public field is not overridden is harder than for private fields, because other libraries can make a declaration with the same name, so we instead need to decide that no other library can create a subtype at all. (And that the current library doesn't override the field itself.)

Deciding that a type cannot have a subtype in another library is tricky.
The simplest rule would be that the class itself, and all subtypes in the same library, are sealed or final, or are enums.

We could add "or private", but then we'd have to make sure there are no public type aliases of the private type, or other ways to leak a reference to the class, which can be used for creating a subclass.

It would make #3215 harder, although if all the same-library types are final, there'd be nothing to apply such a mixin to.

Or we could try to be clever, in some way.
That very easily devolves into a game of wack-a-leak.

So "completely sealed or final subtype hierarchy" could be the way to go.

If your type satisfies that, and no subtype in the same library overrides the final field, then I think it should be safe to promote the field access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants