Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Enable obvious lints #169971

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
Loading
from

Conversation

eernstg
Copy link
Contributor

@eernstg eernstg commented Jun 4, 2025

No description provided.

@eernstg eernstg requested review from a team and matanlurey as code owners June 4, 2025 08:39
@github-actions github-actions bot added a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems platform-ios iOS applications specifically tool Affects the "flutter" command-line tool. See also t: labels. framework flutter/packages/flutter repository. See also f: labels. a: animation Animation APIs f: material design flutter/packages/flutter/material repository. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: internationalization Supporting other languages or locales. (aka i18n) f: scrolling Viewports, list views, slivers, etc. f: cupertino flutter/packages/flutter/cupertino repository d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: routes Navigator, Router, and related APIs. f: gestures flutter/packages/flutter/gestures repository. a: desktop Running on desktop f: focus Focus traversal, gaining or losing focus f: integration_test The flutter/packages/integration_test plugin team-ios Owned by iOS platform team labels Jun 4, 2025
@dkwingsmt dkwingsmt requested a review from justinmc June 4, 2025 18:34
@@ -175,7 +175,7 @@ List<OpacityPeepholeCase> allOpacityPeepholeCases = <OpacityPeepholeCase>[
name: 'Grid of Opacity',
builder: (double v) {
double rowV = v;
double colV = rowV;
var colV = rowV;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is only the 2nd line changed, not the 1st? It's made less pretty.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree this threw me off. Also see from below:

- for (int r = 0; r < numRows; r++, v = 1 - v) {
+ for (var r = 0; r < numRows; r++, v = 1 - v) {

But, as @loic-sharma pointed out, I think this change enables this kind of thing:

- const int magic = 0;
+ const magic = 0;

Is that right @eernstg ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good point!

The lints omit_obvious_local_variable_types, specify_nonobvious_local_variable_types and the corresponding ...property_types all share the same notion of what it takes for an expression to have an 'obvious' type, so any change that moves this borderline will have consequences for all of those. This means that (1) any discussion about where to draw the line should be considered slightly more global in nature than this particular lint, and (2) deciding on something being 'obvious' implies that it is not 'nonobvious', and vice versa.

That said, I think this is a very good example showing something that should be modified.

The reason why line 178 is updated and 177 isn't updated is that rowV is a local variable, and local variables are considered to have an obvious type, but v is a formal parameter, and they aren't considered obvious.

The reason why local variables are considered to have an obvious type is that we only have to scan a limited amount of source code in order to find the declaration of a local variable, and it's considered reasonable to assume that the type of the local variable is obvious when we're looking at its declaration. (That is, if it doesn't have an initializing expression whose type is actually obvious then it should have an explicit type annotation.) I think this rule makes sense, and it certainly helps a lot of expressions to be considered obviously typed.

However, the same reasoning is completely valid for a formal parameter (which is, by the way, considered to be just another kind of local variable in specification texts, which might well be the reason why I missed that one. ;-)

So my response would be: Yes, the lints should be adjusted to consider formal parameters as obviously typed.

About constant variables:

- const int magic = 0;
+ const magic = 0;

Yes, a local constant variable is subject to the same treatment as other local variables. Given that it's equally easy to discern the type of the initializing expression when it is constant and when it is not, I don't think this should be a problem.

Do you think otherwise?

@loic-sharma
Copy link
Member

loic-sharma commented Jun 5, 2025

Overall I think this is an excellent brevity win.

However, I dislike how this removes types when using a named constructor. From a call site, you cannot distinguish a named constructor from a static method. In my opinion, the type from a named constructor invocation is not obvious at the call site. For example:

class Foo {
  Foo.fromEntries(Map<String, dynamic> entries);
  
  static int fromEntry(dynamic entry) => 5;
}

// Before - types are obvious
final Foo one = Foo.fromEntries({'name': 'example', 'value': 42});
final int two = Foo.fromEntry('example');

// After - what's the type of `one`?
// It's not obvious `Foo.fromEntries` is a named constructor
final one = Foo.fromEntries({'name': 'example', 'value': 42});
final int two = Foo.fromEntry('example');

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

Is there any way to do this change in pieces? This PR is so big that my browser can barely handle navigating the Files tab here.

This actually didn't change nearly as much code as I thought it would (despite 2692 files changed). If you look at a file like material/text_field.dart, that is an 1800 line file, but only 3 lines were affected by the lints. I'm not sure if that is an argument in favor or against this change, but it's very interesting.

Overall, skimming through the changes, the code seems to come out looking reasonable.

@@ -175,7 +175,7 @@ List<OpacityPeepholeCase> allOpacityPeepholeCases = <OpacityPeepholeCase>[
name: 'Grid of Opacity',
builder: (double v) {
double rowV = v;
double colV = rowV;
var colV = rowV;
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree this threw me off. Also see from below:

- for (int r = 0; r < numRows; r++, v = 1 - v) {
+ for (var r = 0; r < numRows; r++, v = 1 - v) {

But, as @loic-sharma pointed out, I think this change enables this kind of thing:

- const int magic = 0;
+ const magic = 0;

Is that right @eernstg ?

@LongCatIsLooong
Copy link
Contributor

Overall I think this is an excellent brevity win.

However, I dislike how this removes types when using a named constructor. From a call site, you cannot distinguish a named constructor from a static method. In my opinion, the type from a named constructor invocation is not obvious at the call site. For example:

class Foo {
  Foo.fromEntries(Map<String, dynamic> entries);
  
  static int fromEntry(dynamic entry) => 5;
}

// Before - types are obvious
final Foo one = Foo.fromEntries({'name': 'example', 'value': 42});
final int two = Foo.fromEntry('example');

// After - what's the type of `one`?
// It's not obvious `Foo.fromEntries` is a named constructor
final one = Foo.fromEntries({'name': 'example', 'value': 42});
final int two = Foo.fromEntry('example');

I would argue that most of the time as a reader/reviewer I don't really care what the exact type of a variable is since the typechcker / analyzer would catch any potential typing error, and the variable name is (or at least should be) much more helpful for understanding what it is for. Sometimes I do find it convenient that when the expression returns a collection, the type annotation tells me right away it's a List or a Map (which is usually not reflected in variable names), but I would also have to look at how the variable is used to see if the choice of data structure makes sense so that part of review is not local anyway.

@eernstg
Copy link
Contributor Author

eernstg commented Jun 6, 2025

@loic-sharma wrote:

I dislike how this removes types when using a named constructor. ...
In my opinion, the type from a named constructor invocation is not obvious at the call site.

That's a good point, and I have had discussions about this previously. However, here's a reason why it could be justified, especially in the case where both omit_obvious_local_variable_types and specify_nonobvious_local_variable_types are used together:

If we encounter a declaration of the form var v = C.m(...); then the fact that both lints are enabled shows that the right hand side C.m(...) does indeed have an obvious type (otherwise it would have been T v = C.m(...) for some type T). But this implies that it is a constructor invocation because the static method invocation never has an obvious type.

(Of course, if it's OK to have some local variable declarations with a type annotation whose initializing expression actually calls a constructor then it's enough to have specify_nonobvious_local_variable_types. But it seems nice to me that a reader can think "if and only if".)

@eernstg
Copy link
Contributor Author

eernstg commented Jun 6, 2025

@justinmc wrote:

Is there any way to do this change in pieces? This PR is so big that my browser can barely handle navigating the Files tab here.

We could temporarily relax the lints: Disable always_specify_types and enable type_annotate_public_apis. Then we could transform all the libraries similarly to the changes in this PR, but applied to a smaller set of files in each of a series of PRs. Finally we could enable omit_obvious_local_variable_types and specify_nonobvious_local_variable_types, and fix up anything that might have gone off track in the meantime.

@eernstg
Copy link
Contributor Author

eernstg commented Jun 6, 2025

@LongCatIsLooong wrote:

I would argue that most of the time as a reader/reviewer I don't really care what the exact type of a variable is since the typechcker / analyzer would catch any potential typing error, and the variable name is (or at least should be) much more helpful for understanding what it is for.

The underlying goal with the lint pair omit_obvious_local_variable_types is actually a bit more strict than this: It is always supposed to be easy to discern the declared type of a local variable, either because the initializing expression has an obvious type, or because the declaration has an explicit type annotation. The fact that we're asking for type annotations to be added as well as removed implies that local variables will get a consistent treatment (so there's no need to fight about having or not having a type annotation during reviewing etc. ;-).

If you want to go further and eliminate some type annotations, even in a situation where the type isn't obvious, you could choose to enable omit_obvious_local_variable_types, but not specify_nonobvious_local_variable_types.

If you want to go even further and discourage local variable type annotations entirely then you could forget all about the ...obvious... lints and enable omit_local_variable_types.

(In any case, declarations like Iterable<num> it = <int>[1, 2, 3]; will not be linted, because the declared type is not the one that would be inferred from the initializing expression.)

I think the starting point for this particular effort is that both lints will be enabled.

@loic-sharma
Copy link
Member

@loic-sharma wrote:

I dislike how this removes types when using a named constructor. ...
In my opinion, the type from a named constructor invocation is not obvious at the call site.

... here's a reason why it could be justified, especially in the case where both omit_obvious_local_variable_types and specify_nonobvious_local_variable_types are used together:

If we encounter a declaration of the form var v = C.m(...); then the fact that both lints are enabled shows that the right hand side C.m(...) does indeed have an obvious type (otherwise it would have been T v = C.m(...) for some type T). But this implies that it is a constructor invocation because the static method invocation never has an obvious type.

@eernstg This makes perfect sense for framework developers. However, in my mind the primary target audience for the framework's source code is not framework developers, but rather all Flutter app developers. That includes the next 1 million Flutter app developers, most of whom have never used Dart before and don't know which lints the framework has enabled. I'd prefer if we strove to make the framework's source code obvious to this target demographic, even though it makes the experience a little worse for framework developers.

@eernstg
Copy link
Contributor Author

eernstg commented Jun 7, 2025

@loic-sharma wrote, about considering instance creations of the form C.m(...) as obviously typed:

This makes perfect sense for framework developers. However, in my mind the primary target audience for the framework's source code is not framework developers, but rather all Flutter app developers.

I always had the impression that the framework (and indeed any core element of Flutter, including the engine) would use a style which implies more work and more verbosity in return for added correctness support, but application developers would not be expected to use the same approach. In short, code which is widely (re)used would need more scrutiny than code which is only used by other libraries in the same application.

With that, I'd expect Flutter app developers to already have a culture where code can be less heavily type annotated, compared to Flutter itself.

With that, I'd think it's OK to primarily think about the Flutter framework and engine, and then trust the community to make choices that are optimized for their purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: animation Animation APIs a: desktop Running on desktop a: internationalization Supporting other languages or locales. (aka i18n) a: tests "flutter test", flutter_test, or one of our tests a: text input Entering text in a text field or keyboard related problems d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: cupertino flutter/packages/flutter/cupertino repository f: focus Focus traversal, gaining or losing focus f: gestures flutter/packages/flutter/gestures repository. f: integration_test The flutter/packages/integration_test plugin f: material design flutter/packages/flutter/material repository. f: routes Navigator, Router, and related APIs. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.