dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.24k stars 1.57k forks source link

Pattern matching invalid never matches #53457

Closed Zekfad closed 3 months ago

Zekfad commented 1 year ago

Following code produces composite and doesn't report dead code. Annotations file is too large to post and it's hard to dissect exact class hierarchy, so I've attached it as a text file.

void main(List<String> args) {
  pack.NumberType type = pack.CompositeIntegerType.int16;

  switch(type) {
    case pack.CoreNumberType():
      print('core');
    case pack.CompositeIntegerType():  // The matched value type 'NumberType' can never match the required type 'CompositeIntegerType'.
      print('composite');
  }
}

annotations.dart.txt

lrhn commented 1 year ago

What do you expect the code to do?

The subtypes of NumberType are declared, if I read it correctly, as:

sealed class NumberType implements Comparable<NumberType>  {...}
sealed class CoreNumberType implements NumberType {}
sealed class IntegerType implements NumberType {...}
sealed class FloatType implements NumberType {}
enum CoreIntegerType with _NumberTypeCompare implements IntegerType, CoreNumberType {...}
enum CompositeIntegerType with _NumberTypeCompare implements IntegerType {...}
enum CoreFloatType with _NumberTypeCompare implements FloatType, CoreNumberType {}

That is quite a sealed hierarchy, so we shouldn't need to worry about any subclasses from other libraries.

The enum value, CompositeIntegerType.int16's runtime type is CompositeIntegerType, so it should match the case CompositeIntegerType():.

The case CoreNumberType() exhaust both CoreIntegerType and FloatType (because the latter is sealed, and CoreNumberType exhausts the only subtype CoreFloatType).

Zekfad commented 1 year ago

Code works as expected, but analyzer reports that pattern is unmatchable which is not true:

image

lrhn commented 1 year ago

Thanks. Definitely does not look right.

Pasting this main function into the annotations.dart file, and putting it into DartPad, does give the warning:

warning line 261 • The matched value type 'NumberType' can never match the required type 'CompositeIntegerType'. Try using a different pattern.

and running it prints "composite", so the analyzer is wrong.

denniskaselow commented 1 year ago

As a bit of extra weirdness, the following code shows the same The matched value type 'Foobar' can never match the required type 'Foo'. warning, but uncommenting the Thing-class and case removes the warning. Seems like this warning only occurs if it's only enums implementing the sealed class.

void main() {
  Foobar foobar = Foo.foo;
  final result = switch (foobar) {    
    Foo foo => 'foo: $foo',
    Bar bar => 'bar: $bar',
//     Thing thing => 'this is thing $thing',
  };
  print(result);
}

sealed class Foobar {}
enum Foo implements Foobar { foo; }
enum Bar implements Foobar { bar; }
// class Thing implements Foobar {}
Velkamhell97 commented 1 year ago

So it is a bug of the analyzer ?

btrautmann commented 3 months ago

I have what I think is a similar (the same?) case that does not involve enums but extension types:

extension type TaxId(String value) {
  bool get isValid {
    return value.length == 9;
  }
}

extension type DateOfBirth(DateTime value) {
  bool get isValid {
    return value.isBefore(DateTime.now());
  }

  String get formatted {
    final format = DateFormat(DateTimeFormat.userInput);
    return format.format(value);
  }
}
class HumanRelationship extends BeneficiaryRelationship {
  final DateOfBirth dateOfBirth;

  HumanRelationship({required this.dateOfBirth});
}

class NonHumanTrustRelationship extends BeneficiaryRelationship {
  final TaxId taxId;

  NonHumanTrustRelationship({required this.taxId});
}

class NonHumanEntityRelationship extends BeneficiaryRelationship {
  final TaxId taxId;

  NonHumanEntityRelationship({required this.taxId});
}

Screenshot 2024-07-17 at 12 41 53

Both warnings are:

The matched value type 'TaxId' can never match the required type 'TaxId'.
Try using a different pattern.dart(pattern_never_matches_value_type)

legal_account_beneficiary_draft.dart(78, 16): TaxId is defined in <Location A>
legal_account_beneficiary_draft.dart(78, 16): TaxId is defined in <Location A>

I find it interesting that the warning indicates 2 declaration locations of the extension type but both are the same.

Dart SDK version: 3.3.1 (stable) (Wed Mar 6 13:09:19 2024 +0000) on "macos_arm64"

eernstg commented 3 months ago

I believe the original unjustified warning has been fixed in the bleeding edge version, and this issue can be closed. At least, I don't see that warning when running a fresh analyzer on the original example. @scheglov, do you agree?

For the new comment, @btrautmann, I think it is the same issue and it would also be fixed today.

Example program, doesn't produce any warnings, runs ```dart import 'package:intl/intl.dart'; // import 'package:intl4x/darttime_format.dart'; // Glue code. class DateTimeFormat { static String get userInput => ''; } sealed class BeneficiaryRelationship {} class Beneficiary { final BeneficiaryRelationship relationship; Beneficiary(this.relationship); } // End glue code. extension type TaxId(String value) { bool get isValid { return value.length == 9; } } extension type DateOfBirth(DateTime value) { bool get isValid { return value.isBefore(DateTime.now()); } String get formatted { final format = DateFormat(DateTimeFormat.userInput); return format.format(value); } } class HumanRelationship extends BeneficiaryRelationship { final DateOfBirth dateOfBirth; HumanRelationship({required this.dateOfBirth}); } class NonHumanTrustRelationship extends BeneficiaryRelationship { final TaxId taxId; NonHumanTrustRelationship({required this.taxId}); } class NonHumanEntityRelationship extends BeneficiaryRelationship { final TaxId taxId; NonHumanEntityRelationship({required this.taxId}); } void main() { var beneficiary = Beneficiary(NonHumanTrustRelationship(taxId: TaxId("123456789"))); final subtitle = switch (beneficiary.relationship) { HumanRelationship(:final dateOfBirth) => dateOfBirth.formatted, NonHumanTrustRelationship(:final taxId) => taxId.value, NonHumanEntityRelationship(:final taxId) => taxId.value, }; print(subtitle); } ```
scheglov commented 3 months ago

Support for extension types was fixed as https://github.com/dart-lang/sdk/issues/54561.

eernstg commented 3 months ago

Very good, I'll close the issue.