dart-lang / linter

Linter for Dart.
https://dart.dev/tools/linter-rules
BSD 3-Clause "New" or "Revised" License
629 stars 170 forks source link

`unnecessary_lambdas` false positive when affecting inference #3938

Open FMorschel opened 1 year ago

FMorschel commented 1 year ago

Describe the issue I had a tear-off on my parameter, but I had to change it with a ternary operator. That triggered this false positive.

To Reproduce argument_type_not_assignable triggering on:

void fn({
required void Function(String) parameter,
}) {...}

void function2([String? text]) {...}

void callback() {...}

// Example:
...
  fn(
    parameter: (test) ? function2 : (_) => callback(),
  );
...

When I "solved" that with:

  fn(
    parameter: (test) ? (str) => function2(str) : (_) => callback(), // triggering `unnecessary_lambdas`
  );

Previously I had (and it was working):

  fn(
    parameter: function2,
  );

Expected behavior argument_type_not_assignable not triggering when that solves unnecessary_lambdas or stopping unnecessary_lambdasfrom triggering.

bwilkerson commented 1 year ago

The argument_type_not_assignable diagnostic isn't a lint, it's a compilation error that means that your code won't compile or run.

The problem is that the conditional expression produces a value whose static type is the least upper bound of the types of the then and else expressions (void Function([String?]) and void Function() respectively. Because those signatures don't match, the least upper bound is Function, which really isn't assignable to void Function(String). You could correct the issue by writing

fn(
  parameter: test ? function2 : ([String? _]) => callback(),
);

But there is a bug here, in that unnecessary_lambdas shouldn't have beed produced in this case because the type of the closure isn't the same as the type of the tear-off.

FMorschel commented 1 year ago

Understood.

Though I still don't follow why the (_) on (_) => callback() is not parsed as String when it is placed as a function parameter.

bwilkerson commented 1 year ago

You're right, the type of the parameter is inferred to be String, but the least upper bound of void Function([String?]) and void Function(String) is still be Function because of the difference in nullability.

FMorschel commented 1 year ago

Thank you for the explanation!

pq commented 1 year ago

Yes. Thank you both!

FMorschel commented 1 year ago

You're right, the type of the parameter is inferred to be String, but the least upper bound of void Function([String?]) and void Function(String) is still be Function because of the difference in nullability.

@bwilkerson, is there any documentation on this? I want to understand better, why it couldn't interfere void Function(String) since both could be used as such.

srawlins commented 1 year ago

This comment in the language tracker may be your best source of documentation, and the current state of things.