| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:test/test.dart'; |
| |
| import '../mini_ast.dart'; |
| import '../mini_types.dart'; |
| |
| main() { |
| late Harness h; |
| |
| setUp(() { |
| h = Harness(); |
| }); |
| |
| group('Collection elements:', () { |
| group('If:', () { |
| test('Condition schema', () { |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifElement( |
| expr('dynamic').checkSchema('bool'), |
| expr('Object'), |
| ).checkIR('if(expr(dynamic), celt(expr(Object)), noop)'), |
| ]), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifElement( |
| expr('bool'), |
| expr('Object'), |
| expr('Object'), |
| ).checkIR('if(expr(bool), celt(expr(Object)), celt(expr(Object)))'), |
| ]), |
| ]); |
| }); |
| |
| group('Schema:', () { |
| test('Element type', () { |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifElement( |
| expr('bool'), |
| expr('Object').checkSchema('int'), |
| expr('Object').checkSchema('int'), |
| ).checkIR( |
| 'if(expr(bool), celt(expr(Object)), celt(expr(Object)))'), |
| ]), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('If-case:', () { |
| test('Expression schema', () { |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object').checkSchema('_'), |
| intLiteral(0).pattern, |
| intLiteral(1).checkSchema('int'), |
| ).checkIR('if(expression: expr(Object), pattern: ' |
| 'const(0, matchedType: Object), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: noop)'), |
| ]), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| intLiteral(0).pattern, |
| intLiteral(1).checkSchema('int'), |
| intLiteral(2).checkSchema('int'), |
| ).checkIR('if(expression: expr(Object), pattern: ' |
| 'const(0, matchedType: Object), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: celt(2))'), |
| ]), |
| ]); |
| }); |
| |
| test('With guard', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(x.eq(intLiteral(0))), |
| intLiteral(1).checkSchema('int'), |
| ).checkIR('if(expression: expr(Object), pattern: ' |
| 'varPattern(x, matchedType: Object, staticType: Object), ' |
| 'guard: ==(x, 0), ifTrue: celt(1), ifFalse: noop)'), |
| ]), |
| ]); |
| }); |
| |
| test('Allows refutable patterns', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern(type: 'int'), // has type, refutable |
| intLiteral(1).checkSchema('int'), |
| ).checkIR('if(expression: expr(Object), pattern: varPattern(x, ' |
| 'matchedType: Object, staticType: int), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: noop)'), |
| ]), |
| ]); |
| }); |
| |
| group('Guard not assignable to bool', () { |
| test('int', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('int')..errorId = 'GUARD'), |
| intLiteral(0).checkSchema('int'), |
| ), |
| ]), |
| ], expectedErrors: { |
| 'nonBooleanCondition(node: GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('bool')), |
| intLiteral(0).checkSchema('int'), |
| ), |
| ]), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'int', [ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('dynamic')), |
| intLiteral(0).checkSchema('int'), |
| ), |
| ]), |
| ], expectedErrors: {}); |
| }); |
| }); |
| }); |
| |
| group('Pattern-for-in:', () { |
| group('Expression type:', () { |
| test('Iterable', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'Object', [ |
| patternForInElement( |
| x.pattern(), |
| expr('Iterable<int>'), |
| expr('Object'), |
| ).checkIR('forEach(expr(Iterable<int>), varPattern(x, ' |
| 'matchedType: int, staticType: int), celt(expr(Object)))'), |
| ]), |
| ]); |
| }); |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'Object', [ |
| patternForInElement( |
| x.pattern(), |
| expr('dynamic'), |
| expr('Object'), |
| ).checkIR('forEach(expr(dynamic), varPattern(x, ' |
| 'matchedType: dynamic, staticType: dynamic), ' |
| 'celt(expr(Object)))'), |
| ]), |
| ]); |
| }); |
| test('Object', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'Object', [ |
| (patternForInElement( |
| x.pattern(), |
| expr('Object')..errorId = 'EXPRESSION', |
| expr('Object'), |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Object), varPattern(x, ' |
| 'matchedType: error, staticType: error), ' |
| 'celt(expr(Object)))'), |
| ]), |
| ], expectedErrors: { |
| 'patternForInExpressionIsNotIterable(node: FOR, ' |
| 'expression: EXPRESSION, expressionType: Object)' |
| }); |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When a refutable pattern', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'Object', [ |
| (patternForInElement( |
| x.pattern().nullCheck..errorId = 'PATTERN', |
| expr('Iterable<int?>'), |
| expr('Object'), |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Iterable<int?>), nullCheckPattern(' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'matchedType: int?), celt(expr(Object)))'), |
| ]), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR)', |
| }); |
| }); |
| test('When the variable type is not a subtype of the matched type', () { |
| var x = Var('x'); |
| h.run([ |
| listLiteral(elementType: 'Object', [ |
| (patternForInElement( |
| x.pattern(type: 'String')..errorId = 'PATTERN', |
| expr('Iterable<int>'), |
| expr('Object'), |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Iterable<int>), varPattern(x, ' |
| 'matchedType: int, staticType: String), ' |
| 'celt(expr(Object)))'), |
| ]), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR, matchedType: int, requiredType: String)', |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Expressions:', () { |
| group('cascade:', () { |
| group('IR:', () { |
| test('not null-aware', () { |
| h.run([ |
| expr('dynamic').cascade([ |
| (t) => t.invokeMethod('f', []), |
| (t) => t.invokeMethod('g', []) |
| ]).checkIR('let(t0, expr(dynamic), ' |
| 'let(t1, f(t0), let(t2, g(t0), t0)))'), |
| ]); |
| }); |
| |
| test('null-aware', () { |
| h.run([ |
| expr('dynamic').cascade(isNullAware: true, [ |
| (t) => t.invokeMethod('f', []), |
| (t) => t.invokeMethod('g', []) |
| ]).checkIR('let(t0, expr(dynamic), ' |
| 'if(==(t0, null), t0, let(t1, f(t0), let(t2, g(t0), t0))))'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('integer literal', () { |
| test('double type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: true) |
| .checkType('double') |
| .checkIR('1.0f') |
| .inTypeSchema('double'), |
| ]); |
| }); |
| |
| test('int type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIR('1') |
| .inTypeSchema('int'), |
| ]); |
| }); |
| |
| test('num type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIR('1') |
| .inTypeSchema('num'), |
| ]); |
| }); |
| |
| test('double? type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: true) |
| .checkType('double') |
| .checkIR('1.0f') |
| .inTypeSchema('double?'), |
| ]); |
| }); |
| |
| test('int? type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIR('1') |
| .inTypeSchema('int?'), |
| ]); |
| }); |
| |
| test('unknown type schema', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIR('1') |
| .inTypeSchema('_'), |
| ]); |
| }); |
| |
| test('unrelated type schema', () { |
| // Note: an unrelated type schema can arise in the case of assigning to |
| // a promoted variable, e.g.: |
| // |
| // Object x; |
| // if (x is String) { |
| // x = 1; |
| // } |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIR('1') |
| .inTypeSchema('String'), |
| ]); |
| }); |
| }); |
| |
| group('Switch:', () { |
| test('IR', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| default_.thenExpr(intLiteral(0)), |
| ]).checkIR('switchExpr(expr(int), case(default, 0))'), |
| ]); |
| }); |
| |
| test('scrutinee expression schema', () { |
| h.run([ |
| switchExpr(expr('int').checkSchema('_'), [ |
| default_.thenExpr(intLiteral(0)), |
| ]).inTypeSchema('num'), |
| ]); |
| }); |
| |
| test('body expression schema', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| default_.thenExpr(nullLiteral.checkSchema('C?')), |
| ]).inTypeSchema('C?'), |
| ]); |
| }); |
| |
| test('least upper bound behavior', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('int')), |
| default_.thenExpr(expr('double')), |
| ]).checkType('num') |
| ]); |
| }); |
| |
| test('no cases', () { |
| h.run([switchExpr(expr('A'), []).checkType('Never')]); |
| }); |
| |
| test('guard', () { |
| var i = Var('i'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| i |
| .pattern() |
| .when(i.checkType('int').eq(expr('num')).checkSchema('bool')) |
| .thenExpr(expr('String')), |
| ]).checkIR('switchExpr(expr(int), case(head(varPattern(i, ' |
| 'matchedType: int, staticType: int), ==(i, expr(num)), ' |
| 'variables(i)), expr(String)))'), |
| ]); |
| }); |
| |
| group('Guard not assignable to bool', () { |
| test('int', () { |
| var x = Var('x'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| x |
| .pattern() |
| .when(expr('int')..errorId = 'GUARD') |
| .thenExpr(expr('int')), |
| ]), |
| ], expectedErrors: { |
| 'nonBooleanCondition(node: GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| x.pattern().when(expr('bool')).thenExpr(expr('int')), |
| ]), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| x.pattern().when(expr('dynamic')).thenExpr(expr('int')), |
| ]), |
| ], expectedErrors: {}); |
| }); |
| }); |
| |
| group('Variables:', () { |
| group('logical-or:', () { |
| test('consistent', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switchExpr(expr('double'), [ |
| x1.pattern().or(x2.pattern()).thenExpr(expr('int')), |
| default_.thenExpr(expr('int')), |
| ]).checkType('int').checkIR( |
| 'switchExpr(expr(double), case(head(logicalOrPattern(' |
| 'varPattern(x, matchedType: double, staticType: double), ' |
| 'varPattern(x, matchedType: double, staticType: double), ' |
| 'matchedType: double), true, ' |
| 'variables(double x = [x1, x2])), expr(int)), ' |
| 'case(default, expr(int)))', |
| ), |
| ]); |
| }); |
| group('not consistent:', () { |
| test('different finality', () { |
| var x1 = Var('x', identity: 'x1', isFinal: true); |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switchExpr(expr('double'), [ |
| x1.pattern().or(x2.pattern()).thenExpr(expr('int')), |
| default_.thenExpr(expr('int')), |
| ]).checkType('int').checkIR( |
| 'switchExpr(expr(double), case(head(logicalOrPattern(' |
| 'varPattern(x, matchedType: double, staticType: double), ' |
| 'varPattern(x, matchedType: double, staticType: ' |
| 'double), matchedType: double), true, variables(' |
| 'notConsistent:differentFinalityOrType double x = ' |
| '[x1, x2])), expr(int)), case(default, expr(int)))', |
| ), |
| ], expectedErrors: { |
| 'inconsistentJoinedPatternVariable(variable: x = [x1, x2], ' |
| 'component: x2)' |
| }); |
| }); |
| test('different types', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switchExpr(expr('double'), [ |
| x1 |
| .pattern(type: 'double') |
| .or(x2.pattern(type: 'num')) |
| .thenExpr(expr('int')), |
| default_.thenExpr(expr('int')), |
| ]).checkType('int').checkIR( |
| 'switchExpr(expr(double), case(head(logicalOrPattern(' |
| 'varPattern(x, matchedType: double, staticType: double), ' |
| 'varPattern(x, matchedType: double, staticType: ' |
| 'num), matchedType: double), true, variables(' |
| 'notConsistent:differentFinalityOrType error x = ' |
| '[x1, x2])), expr(int)), case(default, expr(int)))', |
| ), |
| ], expectedErrors: { |
| 'inconsistentJoinedPatternVariable(variable: x = [x1, x2], ' |
| 'component: x2)' |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Inference update 3:', () { |
| void setupTypesForLub() { |
| // Class hierarchy: |
| // A |
| // /\ |
| // / \ |
| // B1<T> B2<T> |
| // | \ / | |
| // | \/ | |
| // | /\ | |
| // | / \ | |
| // C1<T> C2<T> |
| h.addSuperInterfaces('A', (_) => [Type('Object')]); |
| h.addSuperInterfaces('B1', (_) => [Type('A'), Type('Object')]); |
| h.addSuperInterfaces('B2', (_) => [Type('A'), Type('Object')]); |
| h.addSuperInterfaces( |
| 'C1', |
| (args) => [ |
| PrimaryType('B1', args: args), |
| PrimaryType('B2', args: args), |
| Type('A'), |
| Type('Object') |
| ]); |
| h.addSuperInterfaces( |
| 'C2', |
| (args) => [ |
| PrimaryType('B1', args: args), |
| PrimaryType('B2', args: args), |
| Type('A'), |
| Type('Object') |
| ]); |
| h.addLub('C1<Object?>', 'C2<Object?>', 'A'); |
| h.addLub('C1<int>', 'C2<double>', 'A'); |
| h.addLub('B2<Object?>', 'C1<Object?>', 'B2<Object?>'); |
| } |
| |
| test("Context used instead of LUB if LUB doesn't satisfy context", () { |
| setupTypesForLub(); |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('C1<Object?>')), |
| wildcard().thenExpr(expr('C2<Object?>')), |
| ]).checkType('B1<Object?>').inTypeSchema('B1<Object?>'), |
| ]); |
| }); |
| |
| test('Context is converted to a type using greatest closure', () { |
| setupTypesForLub(); |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('C1<int>')), |
| wildcard().thenExpr(expr('C2<double>')), |
| ]).checkType('B1<Object?>').inTypeSchema('B1<_>'), |
| ]); |
| }); |
| |
| test("Context not used if one of the branches doesn't satisfy context", |
| () { |
| setupTypesForLub(); |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('C1<Object?>')), |
| wildcard().thenExpr(expr('B2<Object?>')), |
| ]).checkType('B2<Object?>').inTypeSchema('B1<Object?>'), |
| ]); |
| }); |
| |
| test( |
| "when disabled, LUB always used, even if it doesn't satisfy " |
| "context", () { |
| setupTypesForLub(); |
| h.disableInferenceUpdate3(); |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('C1<Object?>')), |
| wildcard().thenExpr(expr('C2<Object?>')), |
| ]).checkType('A').inTypeSchema('B1<Object?>'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Map:', () { |
| test('downward inference', () { |
| h.run([ |
| mapLiteral(keyType: 'num', valueType: 'Object', [ |
| mapEntry(expr('int').checkSchema('num'), |
| expr('int').checkSchema('Object')) |
| ]), |
| ]); |
| }); |
| |
| test('upward inference', () { |
| h.run([ |
| mapLiteral(keyType: 'int', valueType: 'String', []) |
| .checkType('Map<int, String>'), |
| ]); |
| }); |
| |
| test('IR', () { |
| h.run([ |
| mapLiteral(keyType: 'int', valueType: 'String?', [ |
| mapEntry(intLiteral(0), nullLiteral) |
| ]).checkIR('map(mapEntry(0, null))'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Statements:', () { |
| group('If:', () { |
| test('Condition schema', () { |
| h.run([ |
| if_(expr('dynamic').checkSchema('bool'), [ |
| expr('Object'), |
| ]).checkIR('if(expr(dynamic), block(stmt(expr(Object))), noop)'), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| if_(expr('bool'), [ |
| expr('Object'), |
| ], [ |
| expr('String'), |
| ]).checkIR('if(expr(bool), block(stmt(expr(Object))), ' |
| 'block(stmt(expr(String))))'), |
| ]); |
| }); |
| }); |
| |
| group('If-case:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int').checkSchema('_'), |
| x.pattern(type: 'num'), |
| [], |
| ).checkIR('ifCase(expr(int), ' |
| 'varPattern(x, matchedType: int, staticType: num), variables(x), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| |
| test('With else', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('num'), |
| x.pattern(type: 'int'), |
| [ |
| expr('Object'), |
| ], |
| [ |
| expr('String'), |
| ], |
| ).checkIR('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), variables(x), ' |
| 'true, block(stmt(expr(Object))), block(stmt(expr(String))))'), |
| ]); |
| }); |
| |
| test('With guard', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('num'), |
| x.pattern(type: 'int').when(x.eq(intLiteral(0))), |
| [], |
| ).checkIR('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), variables(x), ' |
| '==(x, 0), block(), noop)'), |
| ]); |
| }); |
| |
| test('Allows refutable patterns', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('num').checkSchema('_'), |
| x.pattern(type: 'int'), |
| [], |
| ).checkIR('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), variables(x), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| |
| group('Guard not assignable to bool', () { |
| test('int', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x.pattern().when(expr('int')..errorId = 'GUARD'), |
| [], |
| ), |
| ], expectedErrors: { |
| 'nonBooleanCondition(node: GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x.pattern().when(expr('bool')), |
| [], |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x.pattern().when(expr('dynamic')), |
| [], |
| ), |
| ], expectedErrors: {}); |
| }); |
| }); |
| }); |
| |
| group('Switch:', () { |
| test('Empty', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [], |
| expectLastCaseTerminates: true, |
| ), |
| ]); |
| }); |
| |
| test('Exhaustive', () { |
| h.addExhaustiveness('E', true); |
| h.run([ |
| switch_( |
| expr('E'), |
| [ |
| expr('E').pattern.then([ |
| break_(), |
| ]), |
| ], |
| expectIsExhaustive: true, |
| ), |
| ]); |
| }); |
| |
| test('No default', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| expectHasDefault: false, |
| expectIsExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('Has default', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| expectHasDefault: true, |
| expectIsExhaustive: true, |
| ), |
| ]); |
| }); |
| |
| test('Last case terminates', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int'), |
| ]), |
| intLiteral(1).pattern.then([ |
| break_(), |
| ]), |
| ], |
| expectLastCaseTerminates: true, |
| ), |
| ]); |
| }); |
| |
| test("Last case doesn't terminate", () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| intLiteral(1).pattern.then([ |
| expr('int'), |
| ]), |
| ], |
| expectLastCaseTerminates: false, |
| ), |
| ]); |
| }); |
| |
| test('Scrutinee type', () { |
| h.run([ |
| switch_(expr('int'), [], expectScrutineeType: 'int'), |
| ]); |
| }); |
| |
| test('const pattern', () { |
| h.run([ |
| switch_( |
| expr('int').checkSchema('_'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(const(0, ' |
| 'matchedType: int), true, variables()), variables()), ' |
| 'block(break())))'), |
| ]); |
| }); |
| |
| group('var pattern:', () { |
| test('untyped', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int').checkSchema('_'), |
| [ |
| x.pattern().then([ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x)), ' |
| 'variables(x)), block(break())))'), |
| ]); |
| }); |
| |
| test('typed', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int').checkSchema('_'), |
| [ |
| x.pattern(type: 'num').then([ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: num), true, variables(x)), ' |
| 'variables(x)), block(break())))'), |
| ]); |
| }); |
| }); |
| |
| test('scrutinee expression schema', () { |
| h.run([ |
| switch_( |
| expr('int').checkSchema('_'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| |
| test('empty final case', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| intLiteral(1).pattern.then([]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(const(0, ' |
| 'matchedType: int), true, variables()), variables()), ' |
| 'block(break())), case(heads(head(const(1, matchedType: int), ' |
| 'true, variables()), variables()), block(synthetic-break())))'), |
| ]); |
| }); |
| |
| test('guard', () { |
| var i = Var('i'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| i |
| .pattern() |
| .when(i.checkType('int').eq(expr('num')).checkSchema('bool')) |
| .then([ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(i, ' |
| 'matchedType: int, staticType: int), ==(i, expr(num)), ' |
| 'variables(i)), variables(i)), block(break())))'), |
| ]); |
| }); |
| |
| group('Variables:', () { |
| test('Independent cases', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().then([ |
| break_(), |
| ]), |
| y.pattern().then([ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x)), ' |
| 'variables(x)), block(break())), case(heads(head(varPattern(y, ' |
| 'matchedType: int, staticType: int), true, variables(y)), ' |
| 'variables(y)), block(break())))'), |
| ]); |
| }); |
| group('Shared case scope:', () { |
| group('Present in both cases:', () { |
| test('With the same type and finality', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| x2.pattern(), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'head(varPattern(x, matchedType: int, staticType: int), ' |
| 'true, variables(x2)), variables(int x = [x1, x2])), ' |
| 'block(break())))'), |
| ]); |
| }); |
| test('With the same type and finality, with logical-or', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| var x3 = Var('x', identity: 'x3'); |
| var x4 = PatternVariableJoin('x', |
| expectedComponents: [x1, x2], identity: 'x4'); |
| PatternVariableJoin('x', expectedComponents: [x4, x3]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern().or(x2.pattern()), |
| x3.pattern(), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(logicalOrPattern(' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'matchedType: int), true, variables(int x = [x1, x2])), ' |
| 'head(varPattern(x, matchedType: int, staticType: int), ' |
| 'true, variables(x3)), variables(int x = ' |
| '[int x = [x1, x2], x3])), block(break())))'), |
| ]); |
| }); |
| group('With different type:', () { |
| test('explicit / explicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(type: 'num'), |
| x2.pattern(type: 'int'), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: num), true, ' |
| 'variables(x1)), head(varPattern(x, matchedType: int, ' |
| 'staticType: int), true, variables(x2)), ' |
| 'variables(notConsistent:differentFinalityOrType error ' |
| 'x = [x1, x2])), block(break())))'), |
| ]); |
| }); |
| test('explicit / implicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(type: 'num'), |
| x2.pattern(), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: num), true, variables(' |
| 'x1)), head(varPattern(x, matchedType: int, ' |
| 'staticType: int), true, variables(x2)), ' |
| 'variables(notConsistent:differentFinalityOrType error ' |
| 'x = [x1, x2])), block(break())))'), |
| ]); |
| }); |
| test('implicit / implicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switch_( |
| expr('List<int>'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| listPattern([x2.pattern()]), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR( |
| 'switch(expr(List<int>), case(heads(head(varPattern(x, ' |
| 'matchedType: List<int>, staticType: List<int>), true, ' |
| 'variables(x1)), head(listPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: ' |
| 'List<int>, requiredType: List<int>), true, ' |
| 'variables(x2)), variables(' |
| 'notConsistent:differentFinalityOrType error ' |
| 'x = [x1, x2])), block(break())))'), |
| ]); |
| }); |
| }); |
| test('With different finality', () { |
| var x1 = Var('x', isFinal: true, identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| x2.pattern(), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'head(varPattern(x, matchedType: int, staticType: int), ' |
| 'true, variables(x2)), variables(' |
| 'notConsistent:differentFinalityOrType int x = [x1, x2])), ' |
| 'block(break())))'), |
| ]); |
| }); |
| }); |
| test('case has, case not', () { |
| var x1 = Var('x', identity: 'x1'); |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| intLiteral(0).pattern, |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'head(const(0, matchedType: int), true, variables()), ' |
| 'variables(notConsistent:sharedCaseAbsent int x = [x1])), ' |
| 'block(break())))'), |
| ]); |
| }); |
| test('case not, case has', () { |
| var x1 = Var('x', identity: 'x1'); |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| intLiteral(0).pattern, |
| x1.pattern(), |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(const(0, ' |
| 'matchedType: int), true, variables()), head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'variables(notConsistent:sharedCaseAbsent int x = [x1])), ' |
| 'block(break())))'), |
| ]); |
| }); |
| test('case has, default', () { |
| var x1 = Var('x', identity: 'x1'); |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| default_, |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'default, variables(notConsistent:sharedCaseHasLabel int x ' |
| '= [x1])), block(break())))'), |
| ]); |
| }); |
| test('case has, with label', () { |
| var x1 = Var('x', identity: 'x1'); |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| x1.pattern(), |
| ], [ |
| break_(), |
| ], hasLabels: true), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(varPattern(x, ' |
| 'matchedType: int, staticType: int), true, variables(x1)), ' |
| 'variables(notConsistent:sharedCaseHasLabel int x = ' |
| '[x1])), block(break())))'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Case completes normally:', () { |
| test('Reported when patterns disabled', () { |
| h.disablePatterns(); |
| h.run([ |
| (switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int'), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: true, |
| )..errorId = 'SWITCH'), |
| ], expectedErrors: { |
| 'switchCaseCompletesNormally(node: SWITCH, caseIndex: 0)' |
| }); |
| }); |
| |
| test('Handles cases that share a body', () { |
| h.disablePatterns(); |
| h.run([ |
| (switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| intLiteral(0).pattern, |
| intLiteral(1).pattern, |
| intLiteral(2).pattern, |
| ], [ |
| expr('int'), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: true, |
| )..errorId = 'SWITCH'), |
| ], expectedErrors: { |
| 'switchCaseCompletesNormally(node: SWITCH, caseIndex: 0)' |
| }); |
| }); |
| |
| test('Not reported when unreachable', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: true, |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('Not reported for final case', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int'), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('Not reported in legacy mode', () { |
| // In legacy mode, the criteria for reporting a switch case that |
| // "falls through" are less accurate (since flow analysis isn't |
| // available in legacy mode). This logic is not currently implemented |
| // in the shared analyzer. |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int'), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('Not reported when patterns enabled', () { |
| // When patterns are enabled, there is an implicit `break` at the end |
| // of every switch body. |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int'), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ], expectedErrors: {}); |
| }); |
| }); |
| |
| group('Case expression type mismatch:', () { |
| group('Pre-null safety:', () { |
| test('subtype', () { |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('num').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('unrelated types', () { |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('String')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: String, nullSafetyEnabled: false)' |
| }); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.enableLegacy(); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('dynamic').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| }); |
| |
| group('Null safe, patterns disabled:', () { |
| test('subtype', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('num')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: num, nullSafetyEnabled: true)' |
| }); |
| }); |
| |
| test('unrelated types', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('String')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: String, nullSafetyEnabled: true)' |
| }); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.disablePatterns(); |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('dynamic')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: false, |
| ) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: dynamic, nullSafetyEnabled: true)' |
| }); |
| }); |
| }); |
| |
| group('Patterns enabled:', () { |
| test('subtype', () { |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('num').pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| |
| test('unrelated types', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('String').pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('dynamic').pattern.then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Pre-merged', () { |
| // The CFE merges cases that share a body at parse time, so make sure we |
| // we can handle merged cases |
| test('Empty', () { |
| // During CFE error recovery, there can be an empty case. |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(variables()), ' |
| 'block(break())))'), |
| ], errorRecoveryOK: true); |
| }); |
| |
| test('Multiple', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| switchStatementMember([ |
| intLiteral(0).pattern, |
| intLiteral(1).pattern, |
| ], [ |
| break_(), |
| ]), |
| ], |
| ).checkIR('switch(expr(int), case(heads(head(const(0, ' |
| 'matchedType: int), true, variables()), head(const(1, ' |
| 'matchedType: int), true, variables()), variables()), ' |
| 'block(break())))'), |
| ]); |
| }); |
| }); |
| |
| group('Guard not assignable to bool', () { |
| test('int', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().when(expr('int')..errorId = 'GUARD').then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ], expectedErrors: { |
| 'nonBooleanCondition(node: GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().when(expr('bool')).then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().when(expr('dynamic')).then([ |
| break_(), |
| ]), |
| ], |
| ), |
| ], expectedErrors: {}); |
| }); |
| }); |
| |
| group('requiresExhaustivenessValidation:', () { |
| test('When a `default` clause is present', () { |
| h.addExhaustiveness('E', true); |
| h.run([ |
| switch_( |
| expr('E'), |
| [ |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| expectRequiresExhaustivenessValidation: false, |
| ), |
| ]); |
| }); |
| |
| test('When the scrutinee is an always-exhaustive type', () { |
| h.addExhaustiveness('E', true); |
| h.run([ |
| switch_( |
| expr('E'), |
| [ |
| expr('E').pattern.then([ |
| break_(), |
| ]), |
| ], |
| expectRequiresExhaustivenessValidation: true, |
| ), |
| ]); |
| }); |
| |
| test('When the scrutinee is not an always-exhaustive type', () { |
| h.addExhaustiveness('C', false); |
| h.run([ |
| switch_( |
| expr('C'), |
| [ |
| expr('C').pattern.then([ |
| break_(), |
| ]), |
| ], |
| expectRequiresExhaustivenessValidation: false, |
| ), |
| ]); |
| }); |
| |
| test('When pattern support is disabled', () { |
| h.disablePatterns(); |
| h.addExhaustiveness('E', true); |
| h.run([ |
| switch_( |
| expr('E'), |
| [ |
| expr('E').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isLegacyExhaustive: true, |
| expectRequiresExhaustivenessValidation: false, |
| ), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Variable declaration:', () { |
| test('initialized, typed', () { |
| var x = Var('x'); |
| h.run([ |
| declare(x, type: 'num', initializer: expr('int').checkSchema('num')) |
| .checkIR('match(expr(int), ' |
| 'varPattern(x, matchedType: int, staticType: num))'), |
| ]); |
| }); |
| |
| test('initialized, untyped', () { |
| var x = Var('x'); |
| h.run([ |
| declare(x, initializer: expr('int').checkSchema('_')) |
| .checkIR('match(expr(int), ' |
| 'varPattern(x, matchedType: int, staticType: int))'), |
| ]); |
| }); |
| |
| test('uninitialized, typed', () { |
| var x = Var('x'); |
| h.run([ |
| declare(x, type: 'int').checkIR( |
| 'declare(varPattern(x, matchedType: int, staticType: int))'), |
| ]); |
| }); |
| |
| test('uninitialized, untyped', () { |
| var x = Var('x'); |
| h.run([ |
| declare(x).checkIR('declare(varPattern(x, matchedType: dynamic, ' |
| 'staticType: dynamic))'), |
| ]); |
| }); |
| |
| test('promoted initializer', () { |
| h.addTypeVariable('T'); |
| var x = Var('x'); |
| h.run([ |
| declare(x, initializer: expr('T&int')).checkIR('match(expr(T&int), ' |
| 'varPattern(x, matchedType: T&int, staticType: T))'), |
| ]); |
| }); |
| |
| test('legal late pattern', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern(), intLiteral(0), isLate: true).checkIR( |
| 'declare_late(x, 0, initializerType: int, staticType: int)'), |
| ]); |
| }); |
| |
| test('illegal refutable pattern', () { |
| h.run([ |
| (match(intLiteral(1).pattern..errorId = 'PATTERN', intLiteral(0)) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| }); |
| |
| group('Pattern-for-in:', () { |
| group('sync', () { |
| group('Expression context type schema:', () { |
| test('Pattern has type', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn(x.pattern(type: 'num'), |
| expr('List<int>').checkSchema('Iterable<num>'), []) |
| .checkIR('forEach(expr(List<int>), varPattern(x, ' |
| 'matchedType: int, staticType: num), block())'), |
| ]); |
| }); |
| test('Pattern does not have type', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn(x.pattern(), |
| expr('List<int>').checkSchema('Iterable<_>'), []) |
| .checkIR('forEach(expr(List<int>), varPattern(x, ' |
| 'matchedType: int, staticType: int), block())'), |
| ]); |
| }); |
| }); |
| group('Expression type:', () { |
| test('Iterable', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn(x.pattern(), expr('Iterable<int>'), []) |
| .checkIR('forEach(expr(Iterable<int>), varPattern(x, ' |
| 'matchedType: int, staticType: int), block())'), |
| ]); |
| }); |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn(x.pattern(), expr('dynamic'), []) |
| .checkIR('forEach(expr(dynamic), varPattern(x, ' |
| 'matchedType: dynamic, staticType: dynamic), block())'), |
| ]); |
| }); |
| test('Object', () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn( |
| x.pattern(), expr('Object')..errorId = 'EXPRESSION', []) |
| ..errorId = 'FOR') |
| .checkIR('forEach(expr(Object), varPattern(x, ' |
| 'matchedType: error, staticType: error), block())'), |
| ], expectedErrors: { |
| 'patternForInExpressionIsNotIterable(node: FOR, ' |
| 'expression: EXPRESSION, expressionType: Object)' |
| }); |
| }); |
| test('error', () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn(x.pattern(), expr('error'), [])) |
| .checkIR('forEach(expr(error), varPattern(x, ' |
| 'matchedType: error, staticType: error), block())'), |
| ]); |
| }); |
| }); |
| group('Refutability:', () { |
| test('When a refutable pattern', () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn(x.pattern().nullCheck..errorId = 'PATTERN', |
| expr('Iterable<int?>'), []) |
| ..errorId = 'FOR') |
| .checkIR('forEach(expr(Iterable<int?>), nullCheckPattern(' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'matchedType: int?), block())'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR)', |
| }); |
| }); |
| test('When the variable type is not a subtype of the matched type', |
| () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn(x.pattern(type: 'String')..errorId = 'PATTERN', |
| expr('Iterable<int>'), []) |
| ..errorId = 'FOR') |
| .checkIR('forEach(expr(Iterable<int>), varPattern(x, ' |
| 'matchedType: int, staticType: String), block())'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR, matchedType: int, requiredType: String)', |
| }); |
| }); |
| }); |
| }); |
| group('async', () { |
| group('Expression context type schema:', () { |
| test('Pattern has type', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn( |
| x.pattern(type: 'num'), |
| expr('Stream<int>').checkSchema('Stream<num>'), |
| [], |
| hasAwait: true, |
| ).checkIR('forEach(expr(Stream<int>), varPattern(x, ' |
| 'matchedType: int, staticType: num), block())'), |
| ]); |
| }); |
| test('Pattern does not have type', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn( |
| x.pattern(), |
| expr('Stream<int>').checkSchema('Stream<_>'), |
| [], |
| hasAwait: true, |
| ).checkIR('forEach(expr(Stream<int>), varPattern(x, ' |
| 'matchedType: int, staticType: int), block())'), |
| ]); |
| }); |
| }); |
| group('Expression type:', () { |
| test('Stream', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn( |
| x.pattern(), |
| expr('Stream<int>'), |
| [], |
| hasAwait: true, |
| ).checkIR('forEach(expr(Stream<int>), varPattern(x, ' |
| 'matchedType: int, staticType: int), block())'), |
| ]); |
| }); |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| patternForIn( |
| x.pattern(), |
| expr('dynamic'), |
| [], |
| hasAwait: true, |
| ).checkIR('forEach(expr(dynamic), varPattern(x, ' |
| 'matchedType: dynamic, staticType: dynamic), block())'), |
| ]); |
| }); |
| test('Object', () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn( |
| x.pattern(), |
| expr('Object')..errorId = 'EXPRESSION', |
| [], |
| hasAwait: true, |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Object), varPattern(x, ' |
| 'matchedType: error, staticType: error), block())'), |
| ], expectedErrors: { |
| 'patternForInExpressionIsNotIterable(node: FOR, ' |
| 'expression: EXPRESSION, expressionType: Object)' |
| }); |
| }); |
| }); |
| group('Refutability:', () { |
| test('When a refutable pattern', () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn( |
| x.pattern().nullCheck..errorId = 'PATTERN', |
| expr('Stream<int?>'), |
| [], |
| hasAwait: true, |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Stream<int?>), nullCheckPattern(' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'matchedType: int?), block())'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR)', |
| }); |
| }); |
| test('When the variable type is not a subtype of the matched type', |
| () { |
| var x = Var('x'); |
| h.run([ |
| (patternForIn( |
| x.pattern(type: 'String')..errorId = 'PATTERN', |
| expr('Stream<int>'), |
| [], |
| hasAwait: true, |
| )..errorId = 'FOR') |
| .checkIR('forEach(expr(Stream<int>), varPattern(x, ' |
| 'matchedType: int, staticType: String), block())'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: FOR, matchedType: int, requiredType: String)', |
| }); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Patterns:', () { |
| group('Cast:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern().as_('int'), expr('num').checkSchema('_')).checkIR( |
| 'match(expr(num), castPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), int, matchedType: num))'), |
| ]); |
| }); |
| |
| group('Refutable context:', () { |
| test('When matched type is a subtype of required type', () { |
| h.run([ |
| ifCase( |
| expr('int'), |
| wildcard().as_('num')..errorId = 'PATTERN', |
| [], |
| ).checkIR('ifCase(expr(int), castPattern(wildcardPattern(' |
| 'matchedType: num), num, matchedType: int), ' |
| 'variables(), true, block(), noop)'), |
| ], expectedErrors: { |
| 'matchedTypeIsSubtypeOfRequired(pattern: PATTERN, ' |
| 'matchedType: int, requiredType: num)', |
| }); |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is a subtype of variable type', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern().as_('num')..errorId = 'PATTERN', expr('int')) |
| .checkIR('match(expr(int), ' |
| 'castPattern(varPattern(x, matchedType: num, ' |
| 'staticType: num), num, matchedType: int))'), |
| ], expectedErrors: { |
| 'matchedTypeIsSubtypeOfRequired(pattern: PATTERN, ' |
| 'matchedType: int, requiredType: num)', |
| }); |
| }); |
| |
| test('When matched type is dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern().as_('num'), expr('dynamic')) |
| .checkIR('match(expr(dynamic), ' |
| 'castPattern(varPattern(x, matchedType: num, ' |
| 'staticType: num), num, matchedType: dynamic))'), |
| ]); |
| }); |
| |
| test('When matched type is not a subtype of variable type', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern().as_('num'), expr('String')) |
| .checkIR('match(expr(String), ' |
| 'castPattern(varPattern(x, matchedType: num, ' |
| 'staticType: num), num, matchedType: String))'), |
| ]); |
| }); |
| }); |
| |
| group('Fully covered due to extension type erasure:', () { |
| test('Cast to representation type', () { |
| // If an `as` pattern fully covers the matched value type due to |
| // extension type erasure, the "matchedTypeIsSubtypeOfRequired" |
| // warning should not be issued. |
| h.addSuperInterfaces('E', (_) => [Type('Object?')]); |
| h.addExtensionTypeErasure('E', 'int'); |
| h.run([ |
| ifCase(expr('E'), wildcard().as_('int'), [ |
| checkReachable(true), |
| ], [ |
| checkReachable(false), |
| ]), |
| ]); |
| }); |
| |
| test('Cast to extension type', () { |
| // If an `as` pattern fully covers the matched value type due to |
| // extension type erasure, the "matchedTypeIsSubtypeOfRequired" |
| // warning should not be issued. |
| h.addSuperInterfaces('E', (_) => [Type('Object?')]); |
| h.addExtensionTypeErasure('E', 'int'); |
| h.run([ |
| ifCase(expr('int'), wildcard().as_('E'), [ |
| checkReachable(true), |
| ], [ |
| checkReachable(false), |
| ]), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Const or literal:', () { |
| test('Refutability', () { |
| h.run([ |
| (match(intLiteral(1).pattern..errorId = 'PATTERN', intLiteral(0)) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| }); |
| |
| group('Map:', () { |
| group('Type schema:', () { |
| test('Explicit type arguments', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPatternWithTypeArguments( |
| keyType: 'bool', |
| valueType: 'int', |
| elements: [ |
| mapPatternEntry(expr('int'), x.pattern()), |
| ], |
| ), |
| expr('dynamic').checkSchema('Map<bool, int>'), |
| ), |
| ]); |
| }); |
| group('Implicit element type:', () { |
| test('No elements', () { |
| h.run([ |
| match( |
| mapPattern([])..errorId = 'PATTERN', |
| expr('dynamic').checkSchema('Map<_, _>'), |
| ), |
| ], expectedErrors: { |
| 'emptyMapPattern(pattern: PATTERN)', |
| }); |
| }); |
| |
| test('Variable patterns', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| mapPattern([ |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int?')), |
| mapPatternEntry(expr('bool'), y.pattern(type: 'num')), |
| ]), |
| expr('dynamic').checkSchema('Map<_, int>'), |
| ), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Static type:', () { |
| test('Explicit type arguments', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('dynamic'), |
| mapPatternWithTypeArguments( |
| keyType: 'bool', |
| valueType: 'int', |
| elements: [ |
| mapPatternEntry( |
| expr('Object').checkSchema('bool'), |
| x.pattern(), |
| ), |
| ], |
| ), |
| [], |
| ).checkIR('ifCase(expr(dynamic), mapPattern(mapPatternEntry(' |
| 'expr(Object), varPattern(x, matchedType: int, staticType: ' |
| 'int)), matchedType: dynamic, requiredType: Map<bool, int>), ' |
| 'variables(x), true, block(), noop)'), |
| ]); |
| }); |
| |
| test('Matched type is a map', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('Map<bool, int>'), |
| mapPattern([ |
| mapPatternEntry( |
| expr('Object').checkSchema('bool'), |
| x.pattern(), |
| ), |
| ]), |
| [], |
| ).checkIR('ifCase(expr(Map<bool, int>), mapPattern(mapPatternEntry(' |
| 'expr(Object), varPattern(x, matchedType: int, staticType: ' |
| 'int)), matchedType: Map<bool, int>, requiredType: ' |
| 'Map<bool, int>), variables(x), true, block(), noop)'), |
| ]); |
| }); |
| |
| test('Matched type is dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('dynamic'), |
| mapPattern([ |
| mapPatternEntry( |
| expr('Object').checkSchema('_'), |
| x.pattern(), |
| ), |
| ]), |
| [], |
| ).checkIR('ifCase(expr(dynamic), mapPattern(mapPatternEntry(' |
| 'expr(Object), varPattern(x, matchedType: dynamic, staticType: ' |
| 'dynamic)), matchedType: dynamic, requiredType: ' |
| 'Map<dynamic, dynamic>), variables(x), true, block(), noop)'), |
| ]); |
| }); |
| |
| test('Matched type is error', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('error'), |
| mapPattern([ |
| mapPatternEntry( |
| expr('Object').checkSchema('_'), |
| x.pattern(), |
| ), |
| ]), |
| [], |
| ).checkIR('ifCase(expr(error), mapPattern(mapPatternEntry(' |
| 'expr(Object), varPattern(x, matchedType: error, staticType: ' |
| 'error)), matchedType: error, requiredType: ' |
| 'Map<error, error>), variables(x), true, block(), noop)'), |
| ]); |
| }); |
| |
| test('Matched type is other', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('String'), |
| mapPattern([ |
| mapPatternEntry( |
| expr('Object').checkSchema('_'), |
| x.pattern(), |
| ), |
| ]), |
| [], |
| ).checkIR('ifCase(expr(String), mapPattern(mapPatternEntry(' |
| 'expr(Object), varPattern(x, matchedType: Object?, staticType: ' |
| 'Object?)), matchedType: String, requiredType: ' |
| 'Map<Object?, Object?>), variables(x), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| |
| group('Refutable context:', () { |
| test('When matched type is a subtype of required type', () { |
| h.run([ |
| match( |
| mapPatternWithTypeArguments( |
| keyType: 'Object', |
| valueType: 'num', |
| elements: [ |
| mapPatternEntry( |
| expr('Object').checkSchema('Object'), |
| wildcard(), |
| ), |
| ], |
| ), |
| expr('Map<bool, int>'), |
| ).checkIR('match(expr(Map<bool, int>), mapPattern(mapPatternEntry(' |
| 'expr(Object), wildcardPattern(matchedType: num)), ' |
| 'matchedType: Map<bool, int>, ' |
| 'requiredType: Map<Object, num>))'), |
| ]); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| match( |
| mapPatternWithTypeArguments( |
| keyType: 'Object', |
| valueType: 'num', |
| elements: [ |
| mapPatternEntry( |
| expr('Object').checkSchema('Object'), |
| wildcard(), |
| ), |
| ], |
| ), |
| expr('dynamic'), |
| ).checkIR('match(expr(dynamic), mapPattern(mapPatternEntry(' |
| 'expr(Object), wildcardPattern(matchedType: num)), ' |
| 'matchedType: dynamic, requiredType: Map<Object, num>))'), |
| ]); |
| }); |
| |
| test('When matched type is not a subtype of required type', () { |
| h.run([ |
| match( |
| mapPatternWithTypeArguments( |
| keyType: 'bool', |
| valueType: 'int', |
| elements: [ |
| mapPatternEntry( |
| expr('Object').checkSchema('bool'), |
| wildcard(), |
| ), |
| ], |
| )..errorId = 'PATTERN', |
| expr('String'), |
| )..errorId = 'CONTEXT', |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: String, ' |
| 'requiredType: Map<bool, int>)' |
| }); |
| }); |
| }); |
| |
| group('Errors:', () { |
| test('Rest pattern first', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPattern([ |
| restPattern()..errorId = 'REST_ELEMENT', |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int')), |
| ]) |
| ..errorId = 'MAP_PATTERN', |
| expr('dynamic'), |
| ), |
| ], expectedErrors: { |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT)' |
| }); |
| }); |
| test('Rest pattern last', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPattern([ |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int')), |
| restPattern()..errorId = 'REST_ELEMENT', |
| ]) |
| ..errorId = 'MAP_PATTERN', |
| expr('dynamic'), |
| ), |
| ], expectedErrors: { |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT)' |
| }); |
| }); |
| test('Two rest elements at the end', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPattern([ |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int')), |
| restPattern()..errorId = 'REST_ELEMENT1', |
| restPattern()..errorId = 'REST_ELEMENT2', |
| ]) |
| ..errorId = 'MAP_PATTERN', |
| expr('dynamic'), |
| ), |
| ], expectedErrors: { |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT1)', |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT2)', |
| }); |
| }); |
| test('Two rest elements not at the end', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPattern([ |
| restPattern()..errorId = 'REST_ELEMENT1', |
| restPattern()..errorId = 'REST_ELEMENT2', |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int')), |
| ]) |
| ..errorId = 'MAP_PATTERN', |
| expr('dynamic'), |
| ), |
| ], expectedErrors: { |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT1)', |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT2)', |
| }); |
| }); |
| test('Rest pattern with subpattern', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| mapPattern([ |
| mapPatternEntry(expr('bool'), x.pattern(type: 'int')), |
| restPattern(wildcard())..errorId = 'REST_ELEMENT', |
| ]) |
| ..errorId = 'MAP_PATTERN', |
| expr('dynamic'), |
| ), |
| ], expectedErrors: { |
| 'restPatternInMap(node: MAP_PATTERN, element: REST_ELEMENT)', |
| }); |
| }); |
| }); |
| }); |
| |
| group('List:', () { |
| group('Type schema:', () { |
| test('Explicit element type', () { |
| var x = Var('x'); |
| h.run([ |
| match(listPattern([x.pattern()], elementType: 'int'), |
| expr('dynamic').checkSchema('List<int>')), |
| ]); |
| }); |
| |
| group('Implicit element type:', () { |
| test('No elements', () { |
| h.run([ |
| match(listPattern([]), expr('dynamic').checkSchema('List<_>')), |
| ]); |
| }); |
| |
| test('Variable patterns', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern( |
| [x.pattern(type: 'int?'), y.pattern(type: 'num')]), |
| expr('dynamic').checkSchema('List<int>')), |
| ]); |
| }); |
| |
| group('Rest pattern:', () { |
| group('With pattern:', () { |
| test('Iterable', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([ |
| restPattern(x.pattern(type: 'Iterable<int>')), |
| ]), |
| expr('List<int>').checkSchema('List<int>'), |
| ), |
| ]); |
| }); |
| test('Not Iterable', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([ |
| restPattern( |
| x.pattern(type: 'String')..errorId = 'VAR(x)', |
| ) |
| ]), |
| expr('List<int>').checkSchema('List<_>'), |
| )..errorId = 'CONTEXT', |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(' |
| 'pattern: VAR(x), context: CONTEXT, matchedType: ' |
| 'List<int>, requiredType: String)' |
| }); |
| }); |
| }); |
| group('Without pattern:', () { |
| test('No other elements', () { |
| h.run([ |
| match( |
| listPattern([restPattern()]), |
| expr('dynamic').checkSchema('List<_>'), |
| ), |
| ]); |
| }); |
| test('Has other elements', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([ |
| x.pattern(type: 'int'), |
| restPattern(), |
| ]), |
| expr('dynamic').checkSchema('List<int>'), |
| ), |
| ]); |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Static type:', () { |
| test('Explicit type', () { |
| var x = Var('x'); |
| h.run([ |
| match(listPattern([x.pattern(type: 'num')], elementType: 'int'), |
| expr('dynamic')) |
| .checkIR('match(expr(dynamic), ' |
| 'listPattern(varPattern(x, matchedType: int, ' |
| 'staticType: num), ' |
| 'matchedType: dynamic, requiredType: List<int>))'), |
| ]); |
| }); |
| |
| test('Matched type is a list', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern([ |
| x.pattern(expectInferredType: 'int'), |
| restPattern( |
| y.pattern(expectInferredType: 'List<int>'), |
| ), |
| ]), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), ...(varPattern(y, ' |
| 'matchedType: List<int>, staticType: List<int>)), ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ]); |
| }); |
| |
| test('Matched type is dynamic', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern([ |
| x.pattern(expectInferredType: 'dynamic'), |
| restPattern(y.pattern(expectInferredType: 'List<dynamic>')), |
| ]), |
| expr('dynamic'), |
| ).checkIR('match(expr(dynamic), listPattern(varPattern(x, ' |
| 'matchedType: dynamic, staticType: dynamic), ...(varPattern(y, ' |
| 'matchedType: List<dynamic>, staticType: List<dynamic>)), ' |
| 'matchedType: dynamic, requiredType: List<dynamic>))'), |
| ]); |
| }); |
| |
| test('Matched type is error', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern([ |
| x.pattern(expectInferredType: 'error'), |
| restPattern(y.pattern(expectInferredType: 'List<error>')), |
| ]), |
| expr('error'), |
| ).checkIR('match(expr(error), listPattern(varPattern(x, ' |
| 'matchedType: error, staticType: error), ...(varPattern(y, ' |
| 'matchedType: List<error>, staticType: List<error>)), ' |
| 'matchedType: error, requiredType: List<error>))'), |
| ]); |
| }); |
| |
| test('Matched type is other', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| ifCase( |
| expr('Object'), |
| listPattern([ |
| x.pattern(expectInferredType: 'Object?'), |
| restPattern(y.pattern(expectInferredType: 'List<Object?>')), |
| ]), |
| [], |
| ).checkIR('ifCase(expr(Object), listPattern(varPattern(x, ' |
| 'matchedType: Object?, staticType: Object?), ...(varPattern(y, ' |
| 'matchedType: List<Object?>, staticType: List<Object?>)), ' |
| 'matchedType: Object, requiredType: List<Object?>), ' |
| 'variables(x, y), true, block(), noop)'), |
| ]); |
| }); |
| |
| group('Rest pattern:', () { |
| test('With pattern', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([restPattern(x.pattern())]), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(...(varPattern(x, ' |
| 'matchedType: List<int>, staticType: List<int>)), ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ]); |
| }); |
| test('Without pattern', () { |
| h.run([ |
| match( |
| listPattern([restPattern()]), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(..., ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is a subtype of pattern type', () { |
| h.run([ |
| match( |
| listPattern([wildcard()], elementType: 'num'), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(wildcardPattern' |
| '(matchedType: num), matchedType: List<int>, ' |
| 'requiredType: List<num>))'), |
| ]); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| match(listPattern([wildcard()], elementType: 'num'), |
| expr('dynamic')) |
| .checkIR('match(expr(dynamic), listPattern(wildcardPattern(' |
| 'matchedType: num), matchedType: dynamic, ' |
| 'requiredType: List<num>))'), |
| ]); |
| }); |
| |
| test('When matched type is not a subtype of variable type', () { |
| h.run([ |
| (match( |
| listPattern([wildcard()], elementType: 'num') |
| ..errorId = 'PATTERN', |
| expr('String')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: String, ' |
| 'requiredType: List<num>)' |
| }); |
| }); |
| |
| test('Sub-refutability', () { |
| h.run([ |
| (match( |
| listPattern([ |
| wildcard(type: 'int')..errorId = 'INT', |
| wildcard(type: 'double')..errorId = 'DOUBLE' |
| ], elementType: 'num'), |
| expr('List<num>')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: INT, ' |
| 'context: CONTEXT, matchedType: num, requiredType: int)', |
| 'patternTypeMismatchInIrrefutableContext(pattern: DOUBLE, ' |
| 'context: CONTEXT, matchedType: num, requiredType: double)' |
| }); |
| }); |
| }); |
| |
| group('Rest pattern:', () { |
| group('Duplicate:', () { |
| test('With pattern', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern([ |
| restPattern(x.pattern())..errorId = 'ORI', |
| restPattern(y.pattern())..errorId = 'DUP', |
| ]) |
| ..errorId = 'LIST_PATTERN', |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(...(varPattern(x, ' |
| 'matchedType: List<int>, staticType: List<int>)), ' |
| '...(varPattern(y, matchedType: List<int>, staticType: ' |
| 'List<int>)), matchedType: List<int>, ' |
| 'requiredType: List<int>))'), |
| ], expectedErrors: { |
| 'duplicateRestPattern(mapOrListPattern: LIST_PATTERN, ' |
| 'original: ORI, ' |
| 'duplicate: DUP)', |
| }); |
| }); |
| test('Without pattern', () { |
| h.run([ |
| match( |
| listPattern([ |
| restPattern()..errorId = 'ORI', |
| restPattern()..errorId = 'DUP', |
| ]) |
| ..errorId = 'LIST_PATTERN', |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(..., ..., ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ], expectedErrors: { |
| 'duplicateRestPattern(mapOrListPattern: LIST_PATTERN, ' |
| 'original: ORI, ' |
| 'duplicate: DUP)', |
| }); |
| }); |
| }); |
| test('First', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([ |
| restPattern(), |
| x.pattern(), |
| ]), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(..., ' |
| 'varPattern(x, matchedType: int, staticType: int), ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ]); |
| }); |
| test('Last', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern([ |
| x.pattern(), |
| restPattern(), |
| ]), |
| expr('List<int>'), |
| ).checkIR('match(expr(List<int>), listPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), ..., ' |
| 'matchedType: List<int>, requiredType: List<int>))'), |
| ]); |
| }); |
| }); |
| |
| test('Match var overlap', () { |
| var x1 = Var('x', identity: 'x1')..errorId = 'x1'; |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| h.run([ |
| match( |
| listPattern([ |
| x1.pattern(), |
| x2.pattern(), |
| ]), |
| expr('List<int>'), |
| ), |
| ], expectedErrors: { |
| 'duplicateVariablePattern(name: x, original: x1, duplicate: x2)', |
| }); |
| }); |
| }); |
| |
| group('Logical-and:', () { |
| test('Type schema', () { |
| h.run([ |
| match( |
| (wildcard(type: 'int?')..errorId = 'WILDCARD1') |
| .and(wildcard(type: 'double?')..errorId = 'WILDCARD2'), |
| nullLiteral.checkSchema('Null')) |
| .checkIR('match(null, logicalAndPattern(wildcardPattern(' |
| 'matchedType: Null), wildcardPattern(matchedType: Null), ' |
| 'matchedType: Null))'), |
| ], expectedErrors: { |
| 'unnecessaryWildcardPattern(pattern: WILDCARD1, ' |
| 'kind: logicalAndPatternOperand)', |
| 'unnecessaryWildcardPattern(pattern: WILDCARD2, ' |
| 'kind: logicalAndPatternOperand)' |
| }); |
| }); |
| |
| test('Refutability', () { |
| h.run([ |
| (match( |
| (wildcard(type: 'int')..errorId = 'LHS') |
| .and(wildcard(type: 'double')..errorId = 'RHS'), |
| expr('num')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: LHS, ' |
| 'context: CONTEXT, matchedType: num, requiredType: int)', |
| 'patternTypeMismatchInIrrefutableContext(pattern: RHS, ' |
| 'context: CONTEXT, matchedType: int, requiredType: double)' |
| }); |
| }); |
| |
| test('Duplicate variable pattern', () { |
| var x1 = Var('x', identity: 'x1')..errorId = 'x1'; |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| h.run([ |
| match(x1.pattern().and(x2.pattern()), expr('int')), |
| ], expectedErrors: { |
| 'duplicateVariablePattern(name: x, original: x1, duplicate: x2)', |
| }); |
| }); |
| }); |
| |
| group('Logical-or:', () { |
| test('Type schema', () { |
| h.run([ |
| (match( |
| wildcard(type: 'int?').or(wildcard(type: 'double?')) |
| ..errorId = 'PATTERN', |
| nullLiteral.checkSchema('_'), |
| )..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| test('Refutability', () { |
| // Note: even though the logical-or contains refutable sub-patterns, we |
| // don't issue errors for them because they would overlap with the error |
| // we're issuing for the logical-or pattern as a whole. |
| h.run([ |
| (match( |
| wildcard(type: 'int').or(wildcard(type: 'double')) |
| ..errorId = 'PATTERN', |
| expr('num')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| group('Variables:', () { |
| group('Should have same types:', () { |
| group('Same:', () { |
| test('explicit / explicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('Object'), |
| x1.pattern(type: 'int').or(x2.pattern(type: 'int')), |
| [], |
| ).checkIR('ifCase(expr(Object), logicalOrPattern(varPattern(x, ' |
| 'matchedType: Object, staticType: int), varPattern(x, ' |
| 'matchedType: Object, staticType: int), ' |
| 'matchedType: Object), variables(int x = [x1, x2]), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| test('explicit / explicit, normalized', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('Object'), |
| x1 |
| .pattern(type: 'Object') |
| .or(x2.pattern(type: 'FutureOr<Object>')), |
| [], |
| ).checkIR('ifCase(expr(Object), logicalOrPattern(varPattern(x, ' |
| 'matchedType: Object, staticType: Object), varPattern(x, ' |
| 'matchedType: Object, staticType: FutureOr<Object>), ' |
| 'matchedType: Object), variables(Object x = [x1, x2]), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| test('explicit / implicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x1.pattern(type: 'int').or(x2.pattern()), |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: int), ' |
| 'variables(int x = [x1, x2]), true, block(), noop)'), |
| ]); |
| }); |
| test('implicit / explicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x1.pattern().or(x2.pattern(type: 'int')), |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: int), ' |
| 'variables(int x = [x1, x2]), true, block(), noop)'), |
| ]); |
| }); |
| test('implicit / implicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x1.pattern().or(x2.pattern()), |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: int), ' |
| 'variables(int x = [x1, x2]), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| group('Not same:', () { |
| test('explicit / explicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('Object'), |
| x1.pattern(type: 'int').or(x2.pattern(type: 'num')), |
| [], |
| ).checkIR('ifCase(expr(Object), logicalOrPattern(varPattern(x, ' |
| 'matchedType: Object, staticType: int), varPattern(x, ' |
| 'matchedType: Object, staticType: num), matchedType: ' |
| 'Object), variables(notConsistent:differentFinalityOrType ' |
| 'error x = [x1, x2]), true, block(), noop)'), |
| ], expectedErrors: { |
| 'inconsistentJoinedPatternVariable(variable: x = [x1, x2], ' |
| 'component: x2)', |
| }); |
| }); |
| test('explicit / implicit', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('num'), |
| x1.pattern(type: 'int').or(x2.pattern()), |
| [], |
| ).checkIR('ifCase(expr(num), logicalOrPattern(varPattern(x, ' |
| 'matchedType: num, staticType: int), varPattern(x, ' |
| 'matchedType: num, staticType: num), matchedType: num), ' |
| 'variables(notConsistent:differentFinalityOrType error x = ' |
| '[x1, x2]), true, block(), noop)'), |
| ], expectedErrors: { |
| 'inconsistentJoinedPatternVariable(variable: x = [x1, x2], ' |
| 'component: x2)', |
| }); |
| }); |
| }); |
| }); |
| test('Should have same finality', () { |
| var x1 = Var('x', isFinal: true, identity: 'x1'); |
| var x2 = Var('x', identity: 'x2')..errorId = 'x2'; |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x1.pattern().or(x2.pattern()), |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: int), ' |
| 'variables(notConsistent:differentFinalityOrType int x = ' |
| '[x1, x2]), true, block(), noop)'), |
| ], expectedErrors: { |
| 'inconsistentJoinedPatternVariable(variable: x = [x1, x2], ' |
| 'component: x2)', |
| }); |
| }); |
| group('Should be present in both branches:', () { |
| test('Both have', () { |
| var x1 = Var('x', identity: 'x1'); |
| var x2 = Var('x', identity: 'x2'); |
| PatternVariableJoin('x', expectedComponents: [x1, x2]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| x1.pattern().or(x2.pattern()), |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), varPattern(x, ' |
| 'matchedType: int, staticType: int), matchedType: int), ' |
| 'variables(int x = [x1, x2]), true, block(), noop)'), |
| ]); |
| }); |
| test('Left has', () { |
| var x1 = Var('x', identity: 'x1')..errorId = 'x1'; |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (x1.pattern().or(wildcard()))..errorId = 'PATTERN', |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(varPattern(x, ' |
| 'matchedType: int, staticType: int), wildcardPattern(' |
| 'matchedType: int), matchedType: int), variables(' |
| 'notConsistent:logicalOr int x = [x1]), true, block(), ' |
| 'noop)'), |
| ], expectedErrors: { |
| 'logicalOrPatternBranchMissingVariable(node: PATTERN, ' |
| 'hasInLeft: true, name: x, variable: x1)', |
| }); |
| }); |
| test('Right has', () { |
| var x1 = Var('x', identity: 'x1')..errorId = 'x1'; |
| PatternVariableJoin('x', expectedComponents: [x1]); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (wildcard().or(x1.pattern()))..errorId = 'PATTERN', |
| [], |
| ).checkIR('ifCase(expr(int), logicalOrPattern(wildcardPattern(' |
| 'matchedType: int), varPattern(x, matchedType: int, ' |
| 'staticType: int), matchedType: int), variables(' |
| 'notConsistent:logicalOr int x = [x1]), true, block(), ' |
| 'noop)'), |
| ], expectedErrors: { |
| 'logicalOrPatternBranchMissingVariable(node: PATTERN, ' |
| 'hasInLeft: false, name: x, variable: x1)', |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Null-assert:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern(type: 'int').nullAssert..errorId = 'PATTERN', |
| expr('int').checkSchema('int?')) |
| .checkIR('match(expr(int), ' |
| 'nullAssertPattern(varPattern(x, matchedType: int, ' |
| 'staticType: int), matchedType: int))'), |
| ], expectedErrors: { |
| 'matchedTypeIsStrictlyNonNullable(pattern: PATTERN, ' |
| 'matchedType: int)' |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is nullable', () { |
| h.run([ |
| match(wildcard().nullAssert, expr('int?')) |
| .checkIR('match(expr(int?), nullAssertPattern(' |
| 'wildcardPattern(matchedType: int), matchedType: int?))'), |
| ]); |
| }); |
| |
| test('When matched type is non-nullable', () { |
| h.run([ |
| match(wildcard().nullAssert..errorId = 'PATTERN', expr('int')) |
| .checkIR('match(expr(int), nullAssertPattern(' |
| 'wildcardPattern(matchedType: int), matchedType: int))'), |
| ], expectedErrors: { |
| 'matchedTypeIsStrictlyNonNullable(pattern: PATTERN, ' |
| 'matchedType: int)' |
| }); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| match(wildcard().nullAssert, expr('dynamic')) |
| .checkIR('match(expr(dynamic), nullAssertPattern(' |
| 'wildcardPattern(matchedType: dynamic), ' |
| 'matchedType: dynamic))'), |
| ]); |
| }); |
| |
| test('Sub-refutability', () { |
| h.run([ |
| (match( |
| (wildcard(type: 'int')..errorId = 'INT').nullAssert |
| ..errorId = 'PATTERN', |
| expr('num')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'matchedTypeIsStrictlyNonNullable(pattern: PATTERN, ' |
| 'matchedType: num)', |
| 'patternTypeMismatchInIrrefutableContext(pattern: INT, ' |
| 'context: CONTEXT, matchedType: num, requiredType: int)' |
| }); |
| }); |
| }); |
| |
| group('Refutable', () { |
| test('When matched type is nullable', () { |
| h.run([ |
| ifCase( |
| expr('int?'), |
| wildcard().nullAssert, |
| [], |
| ).checkIR('ifCase(expr(int?), nullAssertPattern(wildcardPattern(' |
| 'matchedType: int), matchedType: int?), variables(), true, ' |
| 'block(), noop)'), |
| ]); |
| }); |
| test('When matched type is non-nullable', () { |
| h.run([ |
| ifCase( |
| expr('int'), |
| wildcard().nullAssert..errorId = 'PATTERN', |
| [], |
| ).checkIR('ifCase(expr(int), nullAssertPattern(wildcardPattern(' |
| 'matchedType: int), matchedType: int), variables(), true, ' |
| 'block(), noop)'), |
| ], expectedErrors: { |
| 'matchedTypeIsStrictlyNonNullable(pattern: PATTERN, ' |
| 'matchedType: int)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Null-check:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| (match(x.pattern(type: 'int').nullCheck..errorId = 'PATTERN', |
| expr('int').checkSchema('_')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is nullable', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('int?')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| test('When matched type is non-nullable', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('int')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('dynamic')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT)' |
| }); |
| }); |
| |
| test('Sub-refutability', () { |
| |