Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

void_checks: Specify individual types which a FutureOr<void> target can accept #2274

Merged
merged 5 commits into from
Oct 21, 2020

Conversation

srawlins
Copy link
Member

@srawlins srawlins commented Oct 8, 2020

Description

The type of () {} changed to Null Function() (from void Function() in Null Safety. The check performed in void_checks must then compare assignability with Future<dynamic>?.

Fixes dart-lang/sdk#58205

@google-cla google-cla bot added the cla: yes label Oct 8, 2020
@srawlins srawlins requested a review from scheglov October 8, 2020 20:51
_futureDynamicType = context.typeProvider.futureDynamicType;
_futureDynamicType = context.typeSystem.leastUpperBound(
context.typeProvider.futureDynamicType,
context.typeProvider.nullType) as InterfaceType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, if you need this only for FutureOr, instead use typeProvider.futureOrElement.instantiate and provide nullabilitySuffix. LUB is more expensive.

var futureOrVoidType =
context.typeProvider.futureOrType2(context.typeProvider.voidType);
if (expectedType.isVoid && !isTypeAcceptableWhenExpectingVoid(type) ||
expectedType == futureOrVoidType &&
!typeSystem.isAssignableTo(type, _futureDynamicType)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain the portion of expecting FutureOr<void> and typeSystem.isAssignableTo(type, _futureDynamicType)?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure why futureDynamicType was chosen originally. Seems like a strange choice to me. This fix just makes it so that when Null is used where the expected type is FutureOr<void>, then the lint rule will not complain "Hey don't assign a Null to FutureOr<void>.

If I changed this check to "is the type assignable to Future", for example, then it no longer catches a case like:

FutureOr<void> a = 1;

I think because technically this is legal, int is assignable to FutureOr<void>, but the lint wishes to call it out as "Don't assign to void."

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I don't understand which types we want to allow.

When the return/target type is void we allow only void and Null. BTW, it is already an error, but we also get a lint, which is probably not useful.

// @dart = 2.7
import 'dart:async';
void f(FutureOr<int> a) {
  return 0;
}

When the target type is FutureOr<void>, which types do we want to allow?

Copy link
Member Author

@srawlins srawlins Oct 14, 2020

Choose a reason for hiding this comment

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

If the return type of f in your example were FutureOr<void>, I think we want to allow:

  • Future<dynamic> (tested on line 58),
  • Future<void> (not tested?),
  • Future<Null> (not tested?),
  • FutureOr<dynamic> (not tested?),
  • FutureOr<void> (line 71),
  • FutureOr<Null> (newly tested in this PR, line 10)
  • dynamic (line 72),
  • void (untested?),
  • Null (line 73),

and nothing else, I think. Right now the language allows, e.g. an int to be returned where FutureOr<void> is expected.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we can have this nice exhaustive list of types, I think it would be better to check for it explicitly, like we did for void, instead of using a more heavy isAssignableTo.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah ha, good point. I've used isTypeAcceptableWhenExpectingVoid to write a isTypeAcceptableWhenExpectingFutureOrVoid.

var futureOrVoidType =
context.typeProvider.futureOrType2(context.typeProvider.voidType);
if (expectedType.isVoid && !isTypeAcceptableWhenExpectingVoid(type) ||
expectedType == futureOrVoidType &&
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems to me that we are doing it the opposite way here - we should not construct FutureOr<void> and then use == so do force the analyzer to deconstruct it again. Instead check isDartAsyncFutureOr and that the type argument isVoid.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also looked at isTypeAcceptableWhenExpectingVoid and it seems that it can be improved by recursion instead of repeating the checks for void and Null.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah I understand. OK fixed both issues.

@srawlins srawlins changed the title void_checks: Use nullable Future of dynamic void_checks: Specify individual types which a FutureOr<void> target can accept Oct 21, 2020
@srawlins srawlins merged commit f6cf8e8 into dart-lang:master Oct 21, 2020
@srawlins srawlins deleted the fix-2185 branch October 21, 2020 22:13
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

void_checks false positive with FutureOr<void> and NNBD
2 participants