Skip to content
This repository was archived by the owner on Jan 12, 2026. It is now read-only.

extend cases of unnecessary_null_checks/null_check_on_nullable_type_parameter#3258

Merged
pq merged 6 commits into
dart-archive:masterfrom
a14n:extend-null-checks
Mar 15, 2022
Merged

extend cases of unnecessary_null_checks/null_check_on_nullable_type_parameter#3258
pq merged 6 commits into
dart-archive:masterfrom
a14n:extend-null-checks

Conversation

@a14n

@a14n a14n commented Feb 28, 2022

Copy link
Copy Markdown
Contributor

Description

This PR extends the cases where unnecessary_null_checks and null_check_on_nullable_type_parameter apply:

  • in yield expressions
  • in list/set/map literals
  • in await expression

Related to dart-lang/sdk#58666

/cc @srawlins

@coveralls

coveralls commented Feb 28, 2022

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.07%) to 95.681% when pulling 545ba5c on a14n:extend-null-checks into 8c01f68 on dart-lang:master.

@a14n

a14n commented Mar 3, 2022

Copy link
Copy Markdown
Contributor Author

@pq is there something blocking here?

DartType? Function(DartType? type) mayHandleAwait = (type) => type;
if (parent is AwaitExpression) {
mayHandleAwait =
(type) => (type as ParameterizedType?)?.typeArguments.first;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I might be misunderstanding the code, but if the goal of this is to unwrap Future and FutureOr, it will actually attempt unwrap any potentially parameterized type. (The class ParameterizedType only means "a kind of type that is allowed to have type parameters". It doesn't mean "a type that actually has type parameters".)

I think that this will happily unwrap List<int>, and will crash when type is something without type arguments, such as String.

Consider using isDartAsyncFuture and isDartAsyncFutureOr explicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

humm, there was indeed issue with await handling. The return await case is the only one that need a special processing because the target type is really a Future (unlike other case where the unwrap type is provided)

return null;
}
return mayHandleAwait(
(staticType.returnType as ParameterizedType).typeArguments.first);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This and a few places below have the same problem. Consider moving the logic into a single _unwrapFuture method / function.

if (staticType is! FunctionType) {
return null;
}
return (staticType.returnType as ParameterizedType).typeArguments.first;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same here. Having a yield means that the return type is either Iterable<X> or Stream<X>. So a ParameterizedType.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No, having a yield means that the return type is a supertype of one of those types. dynamic works just fine and it isn't a ParameterizedType.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

my bad, sorry and thanks

@pq

pq commented Mar 3, 2022

Copy link
Copy Markdown
Contributor

@pq is there something blocking here?

Sorry was tangled up elsewhere. The big thing is to make sure these lints aren't being used in places where new diagnostics would block an SDK roll. I don't see them used internally on a quick scan.

I do see them enabled in flutter... Do you know if this will flag new cases there?

return null;
}
if (withAwait || parentExpression.body.keyword?.lexeme == 'async') {
// return type is obligatorily a Future<X>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The return type could also be void, dynamic, or Never, so it isn't safe to just cast it to a ParameterizedType; doing so could cause an exception to be thrown.

Even when it's a ParameterizedType, you can't assume that it has type arguments. Every InterfaceType is a ParameterizedType, but not every InterfaceType has type arguments. So asking for the first type argument without first checking that the list isn't empty will result in an exception being thrown.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe a host of negative tests would be good here?

if (staticType is! FunctionType) {
return null;
}
return (staticType.returnType as ParameterizedType).typeArguments.first;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No, having a yield means that the return type is a supertype of one of those types. dynamic works just fine and it isn't a ParameterizedType.

@a14n

a14n commented Mar 3, 2022

Copy link
Copy Markdown
Contributor Author

I do see them enabled in flutter... Do you know if this will flag new cases there?

I see only one additional lint fixed by flutter/flutter#99507

@a14n

a14n commented Mar 14, 2022

Copy link
Copy Markdown
Contributor Author

flutter/flutter#99507 is now merged.

PTAL

@bwilkerson bwilkerson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would be good to have some tests for the cases that the code is guarding against in order to ensure that it is correct.

Also, it didn't occur to me last time, but do we need to worry about subclasses of the targeted types? For example:

class Source extends Iterable<int?> {}

Source f16(int? p) async => p!; // LINT?

What about type aliases:

typedef Source = Iterable<int?>;

If so, we would need to use DartType.asInstanceOf to find the actual value of the type argument (and wouldn't need the isDartCore* tests).

@a14n

a14n commented Mar 14, 2022

Copy link
Copy Markdown
Contributor Author

Also, it didn't occur to me last time, but do we need to worry about subclasses of the targeted types? For example:

class Source extends Iterable<int?> {}

Source f16(int? p) async => p!; // LINT?

Sorry but I don't understand this example.

What about type aliases:

typedef Source = Iterable<int?>;

If so, we would need to use DartType.asInstanceOf to find the actual value of the type argument (and wouldn't need the isDartCore* tests).

I added a test without any change and it seems to work correctly:

typedef F16 = Future<int?>;
F16 f16(int? p) async => p!; // LINT

@bwilkerson bwilkerson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry but I don't understand this example.

I didn't realize that wasn't valid. Sorry for the noise.

Still wouldn't hurt to have some negative tests for the cases we're explicitly guarding against.

@pq pq merged commit de38ee4 into dart-archive:master Mar 15, 2022
@a14n a14n deleted the extend-null-checks branch March 16, 2022 07:10
copybara-service Bot pushed a commit to dart-lang/sdk that referenced this pull request Aug 23, 2023
…arameter (dart-archive/linter#3258)

* extend cases of unnecessary_null_checks/null_check_on_nullable_type_parameter

* fix bad await handling

* add handling of async

* address review comments

* add test for typedef

* add some negative tests
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants