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

New lint: Avoid unstable final fields #3578

Closed
wants to merge 36 commits into from

Conversation

eernstg
Copy link
Member

@eernstg eernstg commented Aug 4, 2022

This PR implements the lint avoid_unstable_final_fields, cf. issue #3440.

The basic idea is that a final field declaration is taken to indicate that the given property is immutable, and hence every override which is not guaranteed to return the same value each time it is invoked will be linted.

class A {
  final int i; // Taken to be immutable, because it's a final variable.
  A(this.i);
}

class B1 implements A {
  static _i = 0;
  int get i => ++_i; // LINT: Does not return the same object every time.
}

class B2 implements A {
  final A a = A(42);
  int get i => a.i; // OK.
}

The PR is intended to serve as a point of reference, facilitating discussion and feedback on the lint and the underlying ruleset, as well as on the implementation.

[Edit: Removed remarks about landing this PR.]

Copy link
Member

@bwilkerson bwilkerson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Just a couple of nits and forward pointers to changes that will land soon.

lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
Copy link
Member Author

@eernstg eernstg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks!

lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
lib/src/rules/avoid_unstable_final_fields.dart Outdated Show resolved Hide resolved
@eernstg eernstg force-pushed the avoid_unstable_final_fields_jul22 branch from aba56ab to 7f97666 Compare August 10, 2022 12:49
@coveralls
Copy link

coveralls commented Aug 10, 2022

Coverage Status

coverage: 95.911% (-0.6%) from 96.551% when pulling f9966a0 on avoid_unstable_final_fields_jul22 into 5ee6a74 on main.

@eernstg eernstg force-pushed the avoid_unstable_final_fields_jul22 branch from de211ad to 1cb730f Compare August 12, 2022 09:07
@srawlins
Copy link
Member

Now that Dart 3 has landed, I'd love to hear your thoughts on continuing down this road, @eernstg . The description above says this is not intended to be landed at this point.

I think I'd be very interested in hearing whether stable getters (dart-lang/language#1518) is still on the table for any upcoming release. It is probably my favorite possibly upcoming feature :)

That being said, this rule would still provide great value even if we never land that feature, yes?

Thanks!

@eernstg
Copy link
Member Author

eernstg commented May 23, 2023

Thanks for the kind words! The 'final getters'/'stable getters' feature does come up in discussions in the language team repeatedly, and I'm not alone in pushing it. But most of the language team is worried about the potential breakage (if we change the meaning of the existing modifier final on non-local variable declarations) or the potentially small impact (if we require a new keyword like stable in order to make any getter stable/final). So it's not a given that any version of this feature will be added to any release of Dart.

Nevertheless, I think it would be great if we could land the lint: It flags situations where a final instance variable is overridden by a getter which is not known to return the same object on repeated invocations, and that seems to be a somewhat delicate situation anyway. It would hardly hurt anyone if the lint exists, as long as it isn't part of a set like 'recommended' or 'core'.

One thing needs to be settled, however: There must be a way to indicate that a given final instance variable induces a getter which is not final/stable. In the current version of the lint I'm using @Object() to indicate that, just because this was something that I could do without changing anything in package meta or dart:core (and also because this annotation is basically never used today). We could use a random value like const unstableGetter = '2d1ae4a0241b54c8ab4f4b55fc546079a1fe4191'; which can be declared anywhere it is needed, and then @unstableGetter can be used to play this role; however, that's probably too brittle to be acceptable.

@srawlins
Copy link
Member

Nevertheless, I think it would be great if we could land the lint

Yeah I think this is a good path forward (to enable the language feature, or as its own end).

One thing needs to be settled, however: There must be a way to indicate that a given final instance variable induces a getter which is not final/stable.

I think we'd be very open to adding an annotation to package:meta. @unstableGetter perhaps. I like the idea of the magic, equal but distant constants that any developer can declare and then use, but I think the UX there is pretty weird. Fine to stay conventional with package:meta. I can start that conversation.

@bwilkerson
Copy link
Member

It shouldn't be a problem to add an annotation to mark an unstable getter. Just to double check though, are we sure we want a lint with an annotation to opt out rather than no lint and an annotation to opt in (that is, assume every getter is unstable unless it's marked as being @stable)? As I understand it, without the @stable annotation users can't mark getters as being stable, only final fields get that designation.

Assuming we want the lint, we'll probably need a fix for cases where the lint breaks the code. Is there any remediation other than marking all overriding getters as being unstable?

If we have the notion of a stable getter, is there anything we can/should do at the invocation sites? I could, for example, imagine suggesting that invocations within a loop could be hoisted (as long as the receiver doesn't change), or suggesting that a single invocation would be more efficient when the same getter is invoked multiple times. These are, of course, things that it would be nice for the compiler to optimize automatically, but if this isn't a language feature the compiler probably can't depend on the semantics.

@eernstg
Copy link
Member Author

eernstg commented May 23, 2023

without the @stable annotation users can't mark getters as being stable, only final fields get that designation.

That is true in the general case. The stable getters / final getters proposal allows declarations like final T get name => e;, which would make it possible to mark any getter as stable, but we don't have that now.

However, it is already possible to do it with an abstract getter: Replace T get name; by abstract final T name;. Those two declarations are equivalent today, but if we consider every final instance variable to have a final/stable getter then the latter would imply that the getter is stable.

If we have the notion of a stable getter, is there anything we can/should do at the invocation sites? I could, for example, imagine suggesting that invocations within a loop could be hoisted (as long as the receiver doesn't change), or suggesting that a single invocation would be more efficient when the same getter is invoked multiple times. These are, of course, things that it would be nice for the compiler to optimize automatically, but if this isn't a language feature the compiler probably can't depend on the semantics.

These things are part of the motivation for having stable/final getters in the first place. Another benefit would be that every stable expression (like aFinalLocalVariable.aStableGetter.aStableGetter) can be promoted.

But those benefits are specific to the language mechanism. As long as we only have a lint we can only used it to give developers a heads-up if there is an apparently-immutable property which isn't actually immutable (that is: a final instance variable which is overridden by a getter which isn't known to be stable).

@eernstg eernstg force-pushed the avoid_unstable_final_fields_jul22 branch from 14eab32 to f2a1621 Compare September 14, 2023 16:57
@bwilkerson
Copy link
Member

Since the last comment on this PR we've moved the linter sources into the sdk repo and stopped accepting PRs against this repo. Would you mind converting this into a Gerrit CL?

@eernstg
Copy link
Member Author

eernstg commented Sep 14, 2023

Sure! I'll do that tomorrow.

@eernstg
Copy link
Member Author

eernstg commented Sep 15, 2023

@eernstg
Copy link
Member Author

eernstg commented Sep 15, 2023

Closing: This effort is now handled via https://dart-review.googlesource.com/c/sdk/+/326342.

@eernstg eernstg closed this Sep 15, 2023
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Oct 12, 2023
This CL transfers the changes made by
dart-lang/linter#3578
to Gerrit.

Change-Id: Ib1eae56d2112e25bcf9916abf27b05bd3de15735
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326342
Commit-Queue: Erik Ernst <eernst@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
mockturtl added a commit to mockturtl/tidy that referenced this pull request Oct 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants