sdk
sdk copied to clipboard
Regression on test coverage of switch expressions.
Setup
Take into consderation the following library code:
sealed class SealedType {}
class A extends SealedType {}
class B extends SealedType {}
String runSwitch(SealedType type) {
return switch (type) {
final A a => 'A',
final B b => 'B',
};
}
And the following test code:
void main() {
test('run', () {
expect(runSwitch(A()), 'A');
expect(runSwitch(B()), 'B');
});
}
(To make your lives easier, i created this sample project)
The problem
When running tests with coverage on dart 3.2.6, we have a report on 100% coverage.
When we do the same on Dart 3.3.0, coverage reports the switch expression as not covered.
Extra info
This was reproduced on MacOS Sonoma using the Dart SDK bundled with Flutter (versions 3.16 and 3.19).
| Item | Details |
|---|---|
| Summary | Switch expressions are not covered by test coverage in Dart 3.3.0. |
| Triage to | area-test (high confidence) |
/cc @liamappelbe @johnniwinther
The same is happening with me. My Dart version is 3.3.0-279.3.beta, my Flutter version is 3.19.0-0.4.pre, and I'm on windows.
What I found especially weird is the fact that if the code is formatted like this:
@override // covered
String runSwitch(SealedType type) => switch (type) { // not covered
final A a => 'A', // covered
final B b => 'B', // covered
};
Then the line containing the method gets marked as not covered, even though the method was called. Somehow the fact that the switch statement is there makes the line get marked as not covered.
@johnniwinther Does the CFE insert a throw on the switch's line? Is it marked with the new error handling flag?
Yes. The generated AST for the code is (using dart pkg/front_end/tool/_fasta/compile.dart --dump-ir)
static method runSwitch(self::SealedType type) → core::String
return block {
core::String #t1;
final synthesized self::SealedType #0#0 = type;
#L1:
{
{
final hoisted self::A a;
if(#0#0 is self::A) {
a = #0#0{self::A};
#t1 = "A";
break #L1;
}
}
{
final hoisted self::B b;
if(#0#0 is self::B) {
b = #0#0{self::B};
#t1 = "B";
break #L1;
}
}
throw{for-error-handling} new _in::ReachabilityError::•("`null` encountered as case in a switch expression with a non-nullable type.");
}
} =>#t1;
Printing this with offsets (using dart pkg/front_end/tool/_fasta/compile.dart --dump-ir --show-offsets) we get
[95]static method runSwitch([116] self::SealedType type) → core::String
[125] return [125] [125] block {
[-1] [-1] core::String #t1;
[133] [133] final synthesized self::SealedType #0#0 = [133] [133] type;
[125] #L1:
[125] {
[170] {
[168] [168] final hoisted self::A a;
[170] if([168] [168] [-1] [-1] #0#0 is self::A) {
[168] [168] [168] a = [-1] [-1] #0#0{self::A};
[-1] [-1] [-1] #t1 = [173] [173] "A";
[170] break #L1;
}
}
[203] {
[201] [201] final hoisted self::B b;
[203] if([201] [201] [-1] [-1] #0#0 is self::B) {
[201] [201] [201] b = [-1] [-1] #0#0{self::B};
[-1] [-1] [-1] #t1 = [206] [206] "B";
[203] break #L1;
}
}
[125] [125] [125] throw{for-error-handling} [125] [125] new _in::ReachabilityError::• [125]([125] "`null` encountered as case in a switch expression with a non-nullable type.");
}
} =>[-1] [-1] #t1;
where offsets 88-140 correspond to the first line.
So except for the ThrowExpression marked as for error handling (throw{for-error-handling}), offsets for the first line are used on the method signature, the ReturnStatement, the contained BlockExpression, the synthesized #0#0 variable, the LabeledStatement and some of the Blocks.
Ok, then this should be fixed when I land the VM plumbing for that flag.
We have a couple customers blocked on using 3.19.x until this is resolved.
Just to confirm my understanding. It looks like this was fixed by https://github.com/dart-lang/sdk/commit/44094abe2f5111e3cbf674855da82c4899d10660 alone and then separately https://github.com/dart-lang/sdk/issues/54005 was filed to clean up the fix to be nicer?
and then separately #54005 was filed to clean up the fix to be nicer?
Actually I think https://github.com/dart-lang/sdk/issues/54005 can be closed. It was just tracking the flag feature.