| // 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:_fe_analyzer_shared/src/type_inference/type_analyzer.dart' |
| hide RecordPatternField, NamedType, RecordType; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart' |
| as shared; |
| 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 context', () { |
| h.run([ |
| ifElement( |
| expr('dynamic').checkContext('bool'), |
| expr('Object').asCollectionElement, |
| ) |
| .checkIr('if(expr(dynamic), celt(expr(Object)), noop)') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| ifElement( |
| expr('bool'), |
| expr('Object').asCollectionElement, |
| expr('Object').asCollectionElement, |
| ) |
| .checkIr('if(expr(bool), celt(expr(Object)), celt(expr(Object)))') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| group('Context:', () { |
| test('Element type', () { |
| h.run([ |
| ifElement( |
| expr('bool'), |
| expr('Object').checkContext('int').asCollectionElement, |
| expr('Object').checkContext('int').asCollectionElement, |
| ) |
| .checkIr( |
| 'if(expr(bool), celt(expr(Object)), celt(expr(Object)))') |
| .inContextElementType('int'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('If-case:', () { |
| test('Expression context', () { |
| h.run([ |
| ifCaseElement( |
| expr('Object').checkContext('?'), |
| intLiteral(0).pattern, |
| intLiteral(1).checkContext('int').asCollectionElement, |
| ) |
| .checkIr('if(expression: expr(Object), pattern: ' |
| 'const(0, matchedType: Object), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: noop)') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| intLiteral(0).pattern, |
| intLiteral(1).checkContext('int').asCollectionElement, |
| intLiteral(2).checkContext('int').asCollectionElement, |
| ) |
| .checkIr('if(expression: expr(Object), pattern: ' |
| 'const(0, matchedType: Object), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: celt(2))') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| test('With guard', () { |
| var x = Var('x'); |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(x.expr.eq(intLiteral(0))), |
| intLiteral(1).checkContext('int').asCollectionElement, |
| ) |
| .checkIr('if(expression: expr(Object), pattern: ' |
| 'varPattern(x, matchedType: Object, staticType: Object), ' |
| 'guard: ==(x, 0), ifTrue: celt(1), ifFalse: noop)') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| test('Allows refutable patterns', () { |
| var x = Var('x'); |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern(type: 'int'), // has type, refutable |
| intLiteral(1).checkContext('int').asCollectionElement, |
| ) |
| .checkIr('if(expression: expr(Object), pattern: varPattern(x, ' |
| 'matchedType: Object, staticType: int), guard: true, ' |
| 'ifTrue: celt(1), ifFalse: noop)') |
| .inContextElementType('int'), |
| ]); |
| }); |
| |
| group('Guard not assignable to bool', () { |
| test('int', () { |
| var x = Var('x'); |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('int')..errorId = 'GUARD'), |
| intLiteral(0).checkContext('int').asCollectionElement, |
| ).inContextElementType('int'), |
| ], expectedErrors: { |
| 'nonBooleanCondition(GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('bool')), |
| intLiteral(0).checkContext('int').asCollectionElement, |
| ).inContextElementType('int'), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| ifCaseElement( |
| expr('Object'), |
| x.pattern().when(expr('dynamic')), |
| intLiteral(0).checkContext('int').asCollectionElement, |
| ).inContextElementType('int'), |
| ], expectedErrors: {}); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Expressions:', () { |
| group('integer literal', () { |
| test('double context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: true) |
| .checkType('double') |
| .checkIr('1.0f') |
| .inContext('double'), |
| ]); |
| }); |
| |
| test('int context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIr('1') |
| .inContext('int'), |
| ]); |
| }); |
| |
| test('num context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIr('1') |
| .inContext('num'), |
| ]); |
| }); |
| |
| test('double? context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: true) |
| .checkType('double') |
| .checkIr('1.0f') |
| .inContext('double?'), |
| ]); |
| }); |
| |
| test('int? context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIr('1') |
| .inContext('int?'), |
| ]); |
| }); |
| |
| test('unknown context', () { |
| h.run([ |
| intLiteral(1, expectConversionToDouble: false) |
| .checkType('int') |
| .checkIr('1') |
| .inContext('?'), |
| ]); |
| }); |
| |
| test('unrelated context', () { |
| // Note: an unrelated context 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') |
| .inContext('String'), |
| ]); |
| }); |
| }); |
| |
| group('Switch:', () { |
| test('IR', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| default_.thenExpr(intLiteral(0)), |
| ]).checkIr('switchExpr(expr(int), case(default, 0))').stmt, |
| ]); |
| }); |
| |
| test('scrutinee expression context', () { |
| h.run([ |
| switchExpr(expr('int').checkContext('?'), [ |
| default_.thenExpr(intLiteral(0)), |
| ]).inContext('num'), |
| ]); |
| }); |
| |
| test('body expression context', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| default_.thenExpr(nullLiteral.checkContext('C?')), |
| ]).inContext('C?'), |
| ]); |
| }); |
| |
| test('least upper bound behavior', () { |
| h.run([ |
| switchExpr(expr('int'), [ |
| intLiteral(0).pattern.thenExpr(expr('int')), |
| default_.thenExpr(expr('double')), |
| ]).checkType('num').stmt |
| ]); |
| }); |
| |
| test('guard', () { |
| var i = Var('i'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| i |
| .pattern() |
| .when(i.expr |
| .checkType('int') |
| .eq(expr('num')) |
| .checkContext('bool')) |
| .thenExpr(expr('String')), |
| ]) |
| .checkIr('switchExpr(expr(int), ' |
| 'case(head(varPattern(i, matchedType: int, ' |
| 'staticType: int), ==(i, expr(num))), expr(String)))') |
| .stmt, |
| ]); |
| }); |
| |
| 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')), |
| ]).stmt, |
| ], expectedErrors: { |
| 'nonBooleanCondition(GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| x.pattern().when(expr('bool')).thenExpr(expr('int')), |
| ]).stmt, |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| switchExpr(expr('int'), [ |
| x.pattern().when(expr('dynamic')).thenExpr(expr('int')), |
| ]).stmt, |
| ], expectedErrors: {}); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Statements:', () { |
| group('If:', () { |
| test('Condition context', () { |
| h.run([ |
| if_(expr('dynamic').checkContext('bool'), [ |
| expr('Object').stmt, |
| ]).checkIr('if(expr(dynamic), block(stmt(expr(Object))), noop)'), |
| ]); |
| }); |
| |
| test('With else', () { |
| h.run([ |
| if_(expr('bool'), [ |
| expr('Object').stmt, |
| ], [ |
| expr('String').stmt, |
| ]).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').checkContext('?'), x.pattern(type: 'num'), [ |
| expr('Object').stmt, |
| ]).checkIr('ifCase(expr(int), ' |
| 'varPattern(x, matchedType: int, staticType: num), true, ' |
| 'block(stmt(expr(Object))), noop)'), |
| ]); |
| }); |
| |
| test('With else', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase(expr('num'), x.pattern(type: 'int'), [ |
| expr('Object').stmt, |
| ], else_: [ |
| expr('String').stmt, |
| ]).checkIr('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), 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.expr.eq(intLiteral(0))), [ |
| expr('Object').stmt, |
| ]).checkIr('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), ==(x, 0), ' |
| 'block(stmt(expr(Object))), noop)'), |
| ]); |
| }); |
| |
| test('Allows refutable patterns', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase(expr('num').checkContext('?'), x.pattern(type: 'int'), [ |
| expr('Object').stmt, |
| ]).checkIr('ifCase(expr(num), ' |
| 'varPattern(x, matchedType: num, staticType: int), true, ' |
| 'block(stmt(expr(Object))), 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(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'), [], |
| isExhaustive: false, expectLastCaseTerminates: true), |
| ]); |
| }); |
| |
| test('Exhaustive', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: true, |
| expectIsExhaustive: true), |
| ]); |
| }); |
| |
| test('No default', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false, |
| expectHasDefault: false, |
| expectIsExhaustive: false), |
| ]); |
| }); |
| |
| test('Has default', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false, |
| expectHasDefault: true, |
| expectIsExhaustive: true), |
| ]); |
| }); |
| |
| test('Last case terminates', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int').stmt, |
| ]), |
| intLiteral(1).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false, |
| expectLastCaseTerminates: true), |
| ]); |
| }); |
| |
| test("Last case doesn't terminate", () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| intLiteral(1).pattern.then([ |
| expr('int').stmt, |
| ]), |
| ], |
| isExhaustive: false, |
| expectLastCaseTerminates: false), |
| ]); |
| }); |
| |
| test('Scrutinee type', () { |
| h.run([ |
| switch_(expr('int'), [], |
| isExhaustive: false, expectScrutineeType: 'int'), |
| ]); |
| }); |
| |
| test('const pattern', () { |
| h.run([ |
| switch_( |
| expr('int').checkContext('?'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true)), ' |
| 'block(break())))'), |
| ]); |
| }); |
| |
| group('var pattern:', () { |
| test('untyped', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int').checkContext('?'), |
| [ |
| x.pattern().then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(x, matchedType: int, ' |
| 'staticType: int), true)), block(break())))'), |
| ]); |
| }); |
| |
| test('typed', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int').checkContext('?'), |
| [ |
| x.pattern(type: 'num').then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(x, matchedType: int, ' |
| 'staticType: num), true)), block(break())))'), |
| ]); |
| }); |
| }); |
| |
| test('scrutinee expression context', () { |
| h.run([ |
| switch_( |
| expr('int').checkContext('?'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('merge cases', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([]), |
| intLiteral(1).pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true), ' |
| 'head(const(1, matchedType: int), true)), block(break())))'), |
| ]); |
| }); |
| |
| test('merge labels', () { |
| var x = Var('x'); |
| var l = Label('l'); |
| h.run([ |
| declare(x, type: 'int?', initializer: expr('int?')), |
| x.expr.as_('int').stmt, |
| switch_( |
| expr('int'), |
| [ |
| l.then(intLiteral(0).pattern).then([]), |
| intLiteral(1).pattern.then([ |
| x.expr.checkType('int?').stmt, |
| break_(), |
| ]), |
| intLiteral(2).pattern.then([ |
| x.expr.checkType('int').stmt, |
| x.write(nullLiteral).stmt, |
| continue_(), |
| ]) |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true), ' |
| 'head(const(1, matchedType: int), true), l), ' |
| 'block(stmt(x), break())), ' |
| 'case(heads(head(const(2, matchedType: int), true)), ' |
| 'block(stmt(x), stmt(null), continue())))'), |
| ]); |
| }); |
| |
| test('empty final case', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| intLiteral(1).pattern.then([]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true)), ' |
| 'block(break())), ' |
| 'case(heads(head(const(1, matchedType: int), true)), ' |
| 'block()))'), |
| ]); |
| }); |
| |
| test('guard', () { |
| var i = Var('i'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| i |
| .pattern() |
| .when(i.expr |
| .checkType('int') |
| .eq(expr('num')) |
| .checkContext('bool')) |
| .then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(i, matchedType: int, ' |
| 'staticType: int), ==(i, expr(num)))), block(break())))'), |
| ]); |
| }); |
| |
| group('missing var:', () { |
| test('default', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().then([]), |
| (default_..errorId = 'DEFAULT').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(DEFAULT, x)' |
| }); |
| }); |
| |
| test('case', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| (intLiteral(0).pattern..errorId = 'CASE(0)').then([]), |
| x.pattern().then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(CASE(0), x)' |
| }); |
| }); |
| |
| test('label', () { |
| var x = Var('x'); |
| var l = Label('l')..errorId = 'LABEL'; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| l.then(x.pattern()).then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(LABEL, x)' |
| }); |
| }); |
| }); |
| |
| group('conflicting var:', () { |
| test('explicit/explicit type', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| (x.pattern(type: 'int')..errorId = 'PATTERN(int x)').then([]), |
| (x.pattern(type: 'num')..errorId = 'PATTERN(num x)').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: PATTERN(num x), type: num, ' |
| 'previousPattern: PATTERN(int x), previousType: int)' |
| }); |
| }); |
| |
| test('explicit/implicit type', () { |
| // TODO(paulberry): not sure whether this should be treated as a |
| // conflict. See https://github.com/dart-lang/language/issues/2424. |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| (x.pattern()..errorId = 'PATTERN(x)').then([]), |
| (x.pattern(type: 'int')..errorId = 'PATTERN(int x)').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'inconsistentMatchVarExplicitness(pattern: PATTERN(int x), ' |
| 'previousPattern: PATTERN(x))' |
| }); |
| }); |
| |
| test('implicit/implicit type', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('List<int>'), |
| [ |
| (x.pattern()..errorId = 'PATTERN(List<int> x)').then([]), |
| listPattern([x.pattern()..errorId = 'PATTERN(int x)']) |
| .then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: PATTERN(int x), type: int, ' |
| 'previousPattern: PATTERN(List<int> x), ' |
| 'previousType: List<int>)' |
| }); |
| }); |
| }); |
| |
| group('Case completes normally:', () { |
| test('Reported when patterns disabled', () { |
| h.patternsEnabled = false; |
| h.run([ |
| (switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int').stmt, |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: true, |
| )..errorId = 'SWITCH'), |
| ], expectedErrors: { |
| 'switchCaseCompletesNormally(SWITCH, 0, 1)' |
| }); |
| }); |
| |
| test('Handles cases that share a body', () { |
| h.patternsEnabled = false; |
| h.run([ |
| (switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([]), |
| intLiteral(1).pattern.then([]), |
| intLiteral(2).pattern.then([ |
| expr('int').stmt, |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: true, |
| )..errorId = 'SWITCH'), |
| ], expectedErrors: { |
| 'switchCaseCompletesNormally(SWITCH, 0, 3)' |
| }); |
| }); |
| |
| test('Not reported when unreachable', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| break_(), |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: true, |
| ), |
| ], expectedErrors: {}); |
| }); |
| |
| test('Not reported for final case', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int').stmt, |
| ]), |
| ], |
| isExhaustive: 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.legacy = true; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int').stmt, |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: 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.patternsEnabled = true; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([ |
| expr('int').stmt, |
| ]), |
| default_.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false, |
| ), |
| ], expectedErrors: {}); |
| }); |
| }); |
| |
| group('Case expression type mismatch:', () { |
| group('Pre-null safety:', () { |
| test('subtype', () { |
| h.legacy = true; |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.legacy = true; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('num').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('unrelated types', () { |
| h.legacy = true; |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('String')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: String, nullSafetyEnabled: false)' |
| }); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.legacy = true; |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.legacy = true; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('dynamic').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| }); |
| |
| group('Null safe, patterns disabled:', () { |
| test('subtype', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('num')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: num, nullSafetyEnabled: true)' |
| }); |
| }); |
| |
| test('unrelated types', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('String')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| ], expectedErrors: { |
| 'caseExpressionTypeMismatch(scrutinee: SCRUTINEE, ' |
| 'caseExpression: EXPRESSION, scrutineeType: int, ' |
| 'caseExpressionType: String, nullSafetyEnabled: true)' |
| }); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.patternsEnabled = false; |
| h.run([ |
| switch_( |
| expr('int')..errorId = 'SCRUTINEE', |
| [ |
| (expr('dynamic')..errorId = 'EXPRESSION').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: 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_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('supertype', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('num').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('unrelated types', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('String').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('dynamic scrutinee', () { |
| h.run([ |
| switch_( |
| expr('dynamic'), |
| [ |
| expr('int').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| |
| test('dynamic case', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| expr('dynamic').pattern.then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ]); |
| }); |
| }); |
| }); |
| |
| 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'), |
| [ |
| mergedCase([]).then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false, |
| ).checkIr('switch(expr(int), case(heads(), block()))'), |
| ], errorRecoveryOk: true); |
| }); |
| |
| test('Multiple', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| mergedCase([intLiteral(0).pattern, intLiteral(1).pattern]) |
| .then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true), ' |
| 'head(const(1, matchedType: int), true)), ' |
| '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_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ], expectedErrors: { |
| 'nonBooleanCondition(GUARD)' |
| }); |
| }); |
| |
| test('bool', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().when(expr('bool')).then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ], expectedErrors: {}); |
| }); |
| |
| test('dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().when(expr('dynamic')).then([ |
| break_(), |
| ]), |
| ], |
| isExhaustive: false), |
| ], expectedErrors: {}); |
| }); |
| }); |
| }); |
| |
| group('Variable declaration:', () { |
| test('initialized, typed', () { |
| var x = Var('x'); |
| h.run([ |
| declare(x, type: 'num', initializer: expr('int').checkContext('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').checkContext('?')) |
| .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.addSubtype('T&int', 'T', true); |
| h.addSubtype('T&int', 'Object', true); |
| h.addFactor('T', 'T&int', '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('match_late(0, varPattern(x, matchedType: int, ' |
| 'staticType: int))'), |
| ]); |
| }); |
| |
| test('illegal late pattern', () { |
| h.run([ |
| (match( |
| listPattern([wildcard()])..errorId = 'PATTERN', expr('List<int>'), |
| isLate: true) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternDoesNotAllowLate(PATTERN)' |
| }); |
| }); |
| |
| test('illegal refutable pattern', () { |
| h.run([ |
| (match(intLiteral(1).pattern..errorId = 'PATTERN', intLiteral(0)) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Patterns:', () { |
| group('Cast:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| x.pattern().as_('int').then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(num), ' |
| 'case(heads(head(castPattern(varPattern(x, matchedType: int, ' |
| 'staticType: int), int, matchedType: num), true)), ' |
| 'block()))'), |
| ]); |
| }); |
| |
| group('Missing var:', () { |
| test('default', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| x.pattern().as_('int').then([]), |
| (default_..errorId = 'DEFAULT').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(DEFAULT, x)' |
| }); |
| }); |
| |
| test('case', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| (intLiteral(0).pattern..errorId = 'CASE_0').then([]), |
| x.pattern().as_('int').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(CASE_0, x)' |
| }); |
| }); |
| |
| test('label', () { |
| var x = Var('x'); |
| var l = Label('l')..errorId = 'LABEL'; |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| l.then(x.pattern().as_('int')).then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'missingMatchVar(LABEL, x)' |
| }); |
| }); |
| }); |
| |
| test('conflicting var:', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| (x.pattern()..errorId = 'INT_PATTERN').as_('int').then([]), |
| (x.pattern()..errorId = 'NUM_PATTERN').as_('num').then([]), |
| ], |
| isExhaustive: true), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: NUM_PATTERN, type: num, ' |
| 'previousPattern: INT_PATTERN, previousType: int)' |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is a subtype of variable type', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern().as_('num'), expr('int')) |
| .checkIr('match(expr(int), ' |
| 'castPattern(varPattern(x, matchedType: num, ' |
| 'staticType: num), num, matchedType: int))'), |
| ]); |
| }); |
| |
| 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('Const or literal:', () { |
| test('Refutability', () { |
| h.run([ |
| (match(intLiteral(1).pattern..errorId = 'PATTERN', intLiteral(0)) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| }); |
| |
| group('List:', () { |
| group('Type schema:', () { |
| test('Explicit element type', () { |
| var x = Var('x'); |
| h.run([ |
| match(listPattern([x.pattern()], elementType: 'int'), |
| expr('dynamic').checkContext('List<int>')), |
| ]); |
| }); |
| |
| group('Implicit element type:', () { |
| test('Empty', () { |
| h.run([ |
| match(listPattern([]), expr('dynamic').checkContext('Object?')), |
| ]); |
| }); |
| |
| test('Non-empty', () { |
| var x = Var('x'); |
| var y = Var('y'); |
| h.run([ |
| match( |
| listPattern( |
| [x.pattern(type: 'int?'), y.pattern(type: 'num')]), |
| expr('dynamic').checkContext('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: dynamic, ' |
| 'staticType: num), ' |
| 'matchedType: dynamic, requiredType: List<int>))'), |
| ]); |
| }); |
| |
| test('Matched type is a list', () { |
| var x = Var('x'); |
| h.run([ |
| match(listPattern([x.pattern(expectInferredType: 'int')]), |
| expr('List<int>')) |
| .checkIr('match(expr(List<int>), ' |
| 'listPattern(varPattern(x, matchedType: int, ' |
| 'staticType: int), matchedType: List<int>, ' |
| 'requiredType: List<int>))'), |
| ]); |
| }); |
| |
| test('Matched type is dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| match(listPattern([x.pattern(expectInferredType: 'dynamic')]), |
| expr('dynamic')) |
| .checkIr('match(expr(dynamic), ' |
| 'listPattern(varPattern(x, matchedType: dynamic, ' |
| 'staticType: dynamic), matchedType: dynamic, ' |
| 'requiredType: List<dynamic>))'), |
| ]); |
| }); |
| |
| test('Matched type is other', () { |
| var x = Var('x'); |
| h.run([ |
| switch_( |
| expr('Object'), |
| [ |
| listPattern([x.pattern(expectInferredType: 'Object?')]) |
| .then([]), |
| ], |
| isExhaustive: false) |
| .checkIr('switch(expr(Object), ' |
| 'case(heads(head(listPattern(varPattern(x, ' |
| 'matchedType: Object?, staticType: Object?), ' |
| 'matchedType: Object, requiredType: List<Object?>), ' |
| 'true)), block()))'), |
| ]); |
| }); |
| }); |
| |
| 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(varPattern(_, matchedType: int, staticType: int), ' |
| '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(varPattern(_, matchedType: dynamic, ' |
| 'staticType: dynamic), 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)' |
| }); |
| }); |
| }); |
| |
| test('Match var overlap', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| listPattern( |
| [x.pattern()..errorId = 'LHS', x.pattern()..errorId = 'RHS']), |
| expr('List<int>')), |
| ], expectedErrors: { |
| 'matchVarOverlap(pattern: RHS, previousPattern: LHS)' |
| }); |
| }); |
| }); |
| |
| group('Logical-and:', () { |
| test('Type schema', () { |
| h.run([ |
| match(wildcard(type: 'int?').and(wildcard(type: 'double?')), |
| nullLiteral.checkContext('Null')) |
| .checkIr('match(null, ' |
| 'logicalAndPattern(varPattern(_, matchedType: Null, ' |
| 'staticType: int?), ' |
| 'varPattern(_, matchedType: Null, staticType: double?), ' |
| 'matchedType: Null))'), |
| ]); |
| }); |
| |
| 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: num, requiredType: double)' |
| }); |
| }); |
| |
| test('Match var overlap', () { |
| var x = Var('x'); |
| h.run([ |
| match( |
| (x.pattern()..errorId = 'LHS').and(x.pattern()..errorId = 'RHS'), |
| expr('int')), |
| ], expectedErrors: { |
| 'matchVarOverlap(pattern: RHS, previousPattern: LHS)' |
| }); |
| }); |
| }); |
| |
| group('Logical-or:', () { |
| test('Type schema', () { |
| h.run([ |
| (match( |
| wildcard(type: 'int?').or(wildcard(type: 'double?')) |
| ..errorId = 'PATTERN', |
| nullLiteral.checkContext('?'), |
| )..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, 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, CONTEXT)' |
| }); |
| }); |
| |
| test('Missing var', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase(expr('int'), x.pattern().or(wildcard()..errorId = 'WILDCARD'), |
| []), |
| ], expectedErrors: { |
| 'missingMatchVar(WILDCARD, x)' |
| }); |
| }); |
| |
| group('Conflicting var:', () { |
| test('explicit/explicit type', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (x.pattern(type: 'int')..errorId = 'PATTERN(int x)') |
| .or(x.pattern(type: 'num')..errorId = 'PATTERN(num x)'), |
| []), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: PATTERN(num x), type: num, ' |
| 'previousPattern: PATTERN(int x), previousType: int)' |
| }); |
| }); |
| |
| test('explicit/implicit type', () { |
| // TODO(paulberry): not sure whether this should be treated as a |
| // conflict. See https://github.com/dart-lang/language/issues/2424. |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (x.pattern()..errorId = 'PATTERN(x)') |
| .or(x.pattern(type: 'int')..errorId = 'PATTERN(int x)'), |
| [], |
| ), |
| ], expectedErrors: { |
| 'inconsistentMatchVarExplicitness(pattern: PATTERN(int x), ' |
| 'previousPattern: PATTERN(x))' |
| }); |
| }); |
| |
| test('implicit/implicit type', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('List<int>'), |
| (x.pattern()..errorId = 'PATTERN(List<int> x)') |
| .or(listPattern([x.pattern()..errorId = 'PATTERN(int x)'])), |
| [], |
| ), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: PATTERN(int x), type: int, ' |
| 'previousPattern: PATTERN(List<int> x), ' |
| 'previousType: List<int>)' |
| }); |
| }); |
| |
| group('Error recovery:', () { |
| test('Each type compared to previous', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (x.pattern(type: 'int')..errorId = 'PATTERN1') |
| .or(x.pattern(type: 'num')..errorId = 'PATTERN2') |
| .or(x.pattern(type: 'num')..errorId = 'PATTERN3') |
| .or(x.pattern(type: 'int')..errorId = 'PATTERN4'), |
| []), |
| ], expectedErrors: { |
| 'inconsistentMatchVar(pattern: PATTERN2, type: num, ' |
| 'previousPattern: PATTERN1, previousType: int)', |
| 'inconsistentMatchVar(pattern: PATTERN4, type: int, ' |
| 'previousPattern: PATTERN3, previousType: num)' |
| }); |
| }); |
| |
| test('Each type explicitness compared to previous', () { |
| var x = Var('x'); |
| h.run([ |
| ifCase( |
| expr('int'), |
| (x.pattern(type: 'int')..errorId = 'PATTERN1') |
| .or(x.pattern()..errorId = 'PATTERN2') |
| .or(x.pattern()..errorId = 'PATTERN3') |
| .or(x.pattern(type: 'int')..errorId = 'PATTERN4'), |
| []), |
| ], expectedErrors: { |
| 'inconsistentMatchVarExplicitness(pattern: PATTERN2, ' |
| 'previousPattern: PATTERN1)', |
| 'inconsistentMatchVarExplicitness(pattern: PATTERN4, ' |
| 'previousPattern: PATTERN3)' |
| }); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Null-assert:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern(type: 'int').nullAssert, |
| expr('int').checkContext('int?')) |
| .checkIr('match(expr(int), ' |
| 'nullAssertPattern(varPattern(x, matchedType: int, ' |
| 'staticType: int), matchedType: int))'), |
| ]); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is nullable', () { |
| h.run([ |
| match(wildcard().nullAssert, expr('int?')) |
| .checkIr('match(expr(int?), ' |
| 'nullAssertPattern(varPattern(_, matchedType: int, ' |
| 'staticType: int), matchedType: int?))'), |
| ]); |
| }); |
| |
| test('When matched type is non-nullable', () { |
| h.run([ |
| match(wildcard().nullAssert, expr('int')) |
| .checkIr('match(expr(int), ' |
| 'nullAssertPattern(varPattern(_, matchedType: int, ' |
| 'staticType: int), matchedType: int))'), |
| ]); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| match(wildcard().nullAssert, expr('dynamic')) |
| .checkIr('match(expr(dynamic), ' |
| 'nullAssertPattern(varPattern(_, matchedType: dynamic, ' |
| 'staticType: dynamic), matchedType: dynamic))'), |
| ]); |
| }); |
| |
| test('Sub-refutability', () { |
| h.run([ |
| (match((wildcard(type: 'int')..errorId = 'INT').nullAssert, |
| expr('num')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: INT, ' |
| 'context: CONTEXT, matchedType: num, requiredType: int)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Null-check:', () { |
| test('Type schema', () { |
| var x = Var('x'); |
| h.run([ |
| (match(x.pattern(type: 'int').nullCheck..errorId = 'PATTERN', |
| expr('int').checkContext('?')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is nullable', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('int?')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| |
| test('When matched type is non-nullable', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('int')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| (match(wildcard().nullCheck..errorId = 'PATTERN', expr('dynamic')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| |
| test('Sub-refutability', () { |
| h.run([ |
| (match(wildcard(type: 'int').nullCheck..errorId = 'PATTERN', |
| expr('num')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Object:', () { |
| group('Refutable:', () { |
| test('inferred', () { |
| h.addDownwardInfer(name: 'B', context: 'A<int>', result: 'B<int>'); |
| h.addMember('B<int>', 'foo', 'int'); |
| h.run([ |
| ifCase( |
| expr('A<int>').checkContext('?'), |
| objectPattern( |
| requiredType: ObjectPatternRequiredType.name('B'), |
| fields: [ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'foo', |
| pattern: Var('foo').pattern(), |
| ), |
| ], |
| ), |
| [], |
| ).checkIr('ifCase(expr(A<int>), objectPattern(varPattern(foo, ' |
| 'matchedType: int, staticType: int), matchedType: A<int>, ' |
| 'requiredType: B<int>), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| |
| group('Irrefutable:', () { |
| test('assignable', () { |
| h.addMember('num', 'foo', 'bool'); |
| h.run([ |
| match( |
| objectPattern( |
| requiredType: ObjectPatternRequiredType.type('num'), |
| fields: [ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'foo', |
| pattern: Var('foo').pattern(), |
| ), |
| ], |
| ), |
| expr('int').checkContext('num'), |
| ).checkIr('match(expr(int), objectPattern(varPattern(foo, ' |
| 'matchedType: bool, staticType: bool), ' |
| 'matchedType: int, requiredType: num))'), |
| ]); |
| }); |
| |
| test('not assignable', () { |
| h.addMember('int', 'foo', 'bool'); |
| h.run([ |
| (match( |
| objectPattern( |
| requiredType: ObjectPatternRequiredType.type('int'), |
| fields: [ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'foo', |
| pattern: Var('foo').pattern(), |
| ), |
| ], |
| )..errorId = 'PATTERN', |
| expr('num').checkContext('int'), |
| )..errorId = 'CONTEXT') |
| .checkIr('match(expr(num), objectPattern(varPattern(foo, ' |
| 'matchedType: bool, staticType: bool), ' |
| 'matchedType: num, requiredType: int))'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: num, requiredType: int)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Record:', () { |
| group('Positional:', () { |
| group('Match dynamic:', () { |
| test('refutable', () { |
| h.run([ |
| ifCase( |
| expr('dynamic').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr( |
| 'ifCase(expr(dynamic), recordPattern(varPattern(a, ' |
| 'matchedType: dynamic, staticType: int), ' |
| 'varPattern(b, matchedType: dynamic, staticType: dynamic), ' |
| 'matchedType: dynamic, requiredType: ' |
| '(Object?, Object?)), true, block(), noop)', |
| ), |
| ]); |
| }); |
| }); |
| group('Match record type:', () { |
| group('Same shape:', () { |
| test('irrefutable', () { |
| h.addSubtype( |
| '(int, String)', |
| '(Object?, Object?)', |
| true, |
| ); |
| h.run([ |
| match( |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| expr('(int, String)').checkContext('(int, ?)'), |
| ).checkIr( |
| 'match(expr((int, String)), recordPattern(varPattern(a, ' |
| 'matchedType: int, staticType: int), varPattern(b, ' |
| 'matchedType: String, staticType: String), ' |
| 'matchedType: (int, String), ' |
| 'requiredType: (Object?, Object?)))') |
| ]); |
| }); |
| }); |
| group('Different shape:', () { |
| test('irrefutable', () { |
| h.addSubtype('(int)', '(Object?, Object?)', false); |
| h.run([ |
| (match( |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(type: 'int') |
| ..errorId = 'VAR(a)', |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]) |
| ..errorId = 'PATTERN', |
| expr('(int,)').checkContext('(int, ?)'), |
| )..errorId = 'CONTEXT') |
| .checkIr('match(expr((int)), recordPattern(varPattern(a, ' |
| 'matchedType: Object?, staticType: int), ' |
| 'varPattern(b, matchedType: Object?, staticType: ' |
| 'Object?), matchedType: (int), requiredType: ' |
| '(Object?, Object?)))'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: VAR(a), ' |
| 'context: CONTEXT, matchedType: Object?, ' |
| 'requiredType: int)', |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: (int), ' |
| 'requiredType: (Object?, Object?))' |
| }); |
| }); |
| group('Refutable:', () { |
| test('too few', () { |
| h.run([ |
| ifCase( |
| expr('(int,)').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr((int)), recordPattern(varPattern(a, ' |
| 'matchedType: Object?, staticType: Object?), ' |
| 'varPattern(b, matchedType: Object?, staticType: ' |
| 'Object?), matchedType: (int), requiredType: ' |
| '(Object?, Object?)), true, block(), noop)'), |
| ]); |
| }); |
| test('too many', () { |
| h.run([ |
| ifCase( |
| expr('(int, String)').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr((int, String)), ' |
| 'recordPattern(varPattern(a, matchedType: Object?, ' |
| 'staticType: Object?), matchedType: (int, String), ' |
| 'requiredType: (Object?)), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| }); |
| }); |
| group('Match other type:', () { |
| test('refutable', () { |
| h.run([ |
| ifCase( |
| expr('X').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr(X), recordPattern(varPattern(a, ' |
| 'matchedType: Object?, staticType: int), varPattern(b, ' |
| 'matchedType: Object?, staticType: Object?), ' |
| 'matchedType: X, requiredType: ' |
| '(Object?, Object?)), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| }); |
| group('Named:', () { |
| group('Match dynamic:', () { |
| test('refutable', () { |
| h.run([ |
| ifCase( |
| expr('dynamic').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'a', |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'b', |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr(dynamic), recordPattern(varPattern(a, ' |
| 'matchedType: dynamic, staticType: int), varPattern(b, ' |
| 'matchedType: dynamic, staticType: dynamic), matchedType: ' |
| 'dynamic, requiredType: ({Object? a, Object? b})), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| }); |
| group('Match record type:', () { |
| group('Same shape:', () { |
| test('irrefutable', () { |
| h.addSubtype( |
| '({int a, String b})', |
| '({Object? a, Object? b})', |
| true, |
| ); |
| h.run([ |
| match( |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'a', |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'b', |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| expr('({int a, String b})').checkContext('({int a, ? b})'), |
| ).checkIr('match(expr(({int a, String b})), ' |
| 'recordPattern(varPattern(a, matchedType: int, ' |
| 'staticType: int), varPattern(b, matchedType: String, ' |
| 'staticType: String), matchedType: ({int a, String b}), ' |
| 'requiredType: ({Object? a, Object? b})))') |
| ]); |
| }); |
| }); |
| group('Different shape:', () { |
| test('irrefutable', () { |
| h.addSubtype('({int a})', '({Object? a, Object? b})', false); |
| h.run([ |
| (match( |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'a', |
| pattern: Var('a').pattern(type: 'int') |
| ..errorId = 'VAR(a)', |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'b', |
| pattern: Var('b').pattern(), |
| ), |
| ]) |
| ..errorId = 'PATTERN', |
| expr('({int a})').checkContext('({int a, ? b})'), |
| )..errorId = 'CONTEXT') |
| .checkIr('match(expr(({int a})), ' |
| 'recordPattern(varPattern(a, matchedType: Object?, ' |
| 'staticType: int), varPattern(b, matchedType: Object?, ' |
| 'staticType: Object?), matchedType: ({int a}), ' |
| 'requiredType: ({Object? a, Object? b})))'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: VAR(a), ' |
| 'context: CONTEXT, matchedType: Object?, ' |
| 'requiredType: int)', |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: ({int a}), ' |
| 'requiredType: ({Object? a, Object? b}))', |
| }); |
| }); |
| group('Refutable:', () { |
| test('too few', () { |
| h.run([ |
| ifCase( |
| expr('({int a})').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'a', |
| pattern: Var('a').pattern(), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'b', |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr(({int a})), ' |
| 'recordPattern(varPattern(a, matchedType: Object?, ' |
| 'staticType: Object?), varPattern(b, matchedType: ' |
| 'Object?, staticType: Object?), matchedType: ' |
| '({int a}), requiredType: ({Object? a, Object? b})), ' |
| 'true, block(), noop)'), |
| ]); |
| }); |
| test('too many', () { |
| h.run([ |
| ifCase( |
| expr('({int a, String b})').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: 'a', |
| pattern: Var('a').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr(({int a, String b})), ' |
| 'recordPattern(varPattern(a, matchedType: Object?, ' |
| 'staticType: Object?), matchedType: ({int a, String b}), ' |
| 'requiredType: ({Object? a})), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| }); |
| }); |
| group('Match other type:', () { |
| test('refutable', () { |
| h.run([ |
| ifCase( |
| expr('X').checkContext('?'), |
| recordPattern([ |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('a').pattern(type: 'int'), |
| ), |
| shared.RecordPatternField( |
| node: RecordPatternField(), |
| name: null, |
| pattern: Var('b').pattern(), |
| ), |
| ]), |
| [], |
| ).checkIr('ifCase(expr(X), recordPattern(varPattern(a, ' |
| 'matchedType: Object?, staticType: int), varPattern(b, ' |
| 'matchedType: Object?, staticType: Object?), ' |
| 'matchedType: X, requiredType: ' |
| '(Object?, Object?)), true, block(), noop)'), |
| ]); |
| }); |
| }); |
| }); |
| }); |
| |
| group('Relational:', () { |
| test('Refutability', () { |
| h.run([ |
| (match( |
| relationalPattern( |
| RelationalOperatorResolution<Type>( |
| isEquality: false, |
| parameterType: Type('num'), |
| returnType: Type('bool'), |
| ), |
| intLiteral(0).checkContext('num'), |
| )..errorId = 'PATTERN', |
| intLiteral(1).checkContext('?'), |
| )..errorId = 'CONTEXT') |
| .checkIr('match(1, relationalPattern(0, matchedType: int))'), |
| ], expectedErrors: { |
| 'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)' |
| }); |
| }); |
| test('no operator', () { |
| h.run([ |
| ifCase( |
| expr('int').checkContext('?'), |
| relationalPattern( |
| null, |
| intLiteral(0).checkContext('?'), |
| ), |
| [], |
| ).checkIr('ifCase(expr(int), relationalPattern(0, ' |
| 'matchedType: int), true, block(), noop)') |
| ]); |
| }); |
| group('Has operator:', () { |
| test('int >=', () { |
| h.run([ |
| ifCase( |
| expr('int').checkContext('?'), |
| relationalPattern( |
| RelationalOperatorResolution<Type>( |
| isEquality: false, |
| parameterType: Type('num'), |
| returnType: Type('bool'), |
| ), |
| intLiteral(0).checkContext('num'), |
| ), |
| [], |
| ).checkIr('ifCase(expr(int), relationalPattern(0, ' |
| 'matchedType: int), true, block(), noop)') |
| ]); |
| }); |
| test('Object == nullable', () { |
| h.run([ |
| ifCase( |
| expr('Object').checkContext('?'), |
| relationalPattern( |
| RelationalOperatorResolution<Type>( |
| isEquality: true, |
| parameterType: Type('Object'), |
| returnType: Type('bool'), |
| ), |
| expr('int?').checkContext('Object'), |
| ), |
| [], |
| ).checkIr('ifCase(expr(Object), relationalPattern(expr(int?), ' |
| 'matchedType: Object), true, block(), noop)') |
| ]); |
| }); |
| test('Object != nullable', () { |
| h.run([ |
| ifCase( |
| expr('Object').checkContext('?'), |
| relationalPattern( |
| RelationalOperatorResolution<Type>( |
| isEquality: true, |
| parameterType: Type('Object'), |
| returnType: Type('bool'), |
| ), |
| expr('int?').checkContext('Object'), |
| ), |
| [], |
| ).checkIr('ifCase(expr(Object), relationalPattern(expr(int?), ' |
| 'matchedType: Object), true, block(), noop)') |
| ]); |
| }); |
| test('argument type not assignable', () { |
| h.run([ |
| ifCase( |
| expr('int').checkContext('?'), |
| relationalPattern( |
| RelationalOperatorResolution<Type>( |
| isEquality: false, |
| parameterType: Type('num'), |
| returnType: Type('bool'), |
| ), |
| expr('String')..errorId = 'OPERAND', |
| ), |
| [], |
| ).checkIr('ifCase(expr(int), relationalPattern(expr(String), ' |
| 'matchedType: int), true, block(), noop)') |
| ], expectedErrors: { |
| 'argumentTypeNotAssignable(argument: OPERAND, ' |
| 'argumentType: String, parameterType: num)' |
| }); |
| }); |
| test('return type is not assignable to bool', () { |
| h.run([ |
| ifCase( |
| expr('A').checkContext('?'), |
| relationalPattern( |
| RelationalOperatorResolution( |
| isEquality: false, |
| parameterType: Type('Object'), |
| returnType: Type('int'), |
| ), |
| expr('String').checkContext('Object'), |
| )..errorId = 'PATTERN', |
| [], |
| ).checkIr('ifCase(expr(A), relationalPattern(expr(String), ' |
| 'matchedType: A), true, block(), noop)') |
| ], expectedErrors: { |
| 'relationalPatternOperatorReturnTypeNotAssignableToBool(' |
| 'node: PATTERN, returnType: int)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Variable:', () { |
| group('Refutability:', () { |
| test('When matched type is a subtype of variable type', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern(type: 'num'), expr('int')) |
| .checkIr('match(expr(int), ' |
| 'varPattern(x, matchedType: int, staticType: num))'), |
| ]); |
| }); |
| |
| test('When matched type is dynamic', () { |
| var x = Var('x'); |
| h.run([ |
| match(x.pattern(type: 'num'), expr('dynamic')) |
| .checkIr('match(expr(dynamic), ' |
| 'varPattern(x, matchedType: dynamic, staticType: num))'), |
| ]); |
| }); |
| |
| test('When matched type is not a subtype of variable type', () { |
| var x = Var('x'); |
| h.run([ |
| (match(x.pattern(type: 'num')..errorId = 'PATTERN', expr('String')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: String, requiredType: num)' |
| }); |
| }); |
| }); |
| }); |
| |
| group('Wildcard:', () { |
| test('Untyped', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| wildcard().then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true)), block()))'), |
| ]); |
| }); |
| |
| test('Typed', () { |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| wildcard(type: 'int').then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(num), ' |
| 'case(heads(head(varPattern(_, matchedType: num, ' |
| 'staticType: int), true)), block()))'), |
| ]); |
| }); |
| |
| group('Exempt from errors:', () { |
| group('Missing var:', () { |
| test('default', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| wildcard().then([]), |
| default_.then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true), default), ' |
| 'block()))'), |
| ]); |
| }); |
| |
| test('case', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| intLiteral(0).pattern.then([]), |
| wildcard().then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(const(0, matchedType: int), true), ' |
| 'head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true)), block()))'), |
| ]); |
| }); |
| |
| test('label', () { |
| var l = Label('l'); |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| l.then(wildcard()).then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true), l), ' |
| 'block()))'), |
| ]); |
| }); |
| }); |
| |
| group('conflicting var:', () { |
| test('explicit/explicit type', () { |
| h.run([ |
| switch_( |
| expr('num'), |
| [ |
| wildcard(type: 'int').then([]), |
| wildcard(type: 'num').then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(num), ' |
| 'case(heads(head(varPattern(_, matchedType: num, ' |
| 'staticType: int), true), ' |
| 'head(varPattern(_, matchedType: num, ' |
| 'staticType: num), true)), block()))'), |
| ]); |
| }); |
| |
| test('explicit/implicit type', () { |
| h.run([ |
| switch_( |
| expr('int'), |
| [ |
| wildcard().then([]), |
| wildcard(type: 'int').then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(int), ' |
| 'case(heads(head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true), ' |
| 'head(varPattern(_, matchedType: int, ' |
| 'staticType: int), true)), block()))'), |
| ]); |
| }); |
| |
| test('implicit/implicit type', () { |
| h.run([ |
| switch_( |
| expr('List<int>'), |
| [ |
| wildcard().then([]), |
| listPattern([wildcard()]).then([]), |
| ], |
| isExhaustive: true) |
| .checkIr('switch(expr(List<int>), ' |
| 'case(heads(head(varPattern(_, matchedType: List<int>, ' |
| 'staticType: List<int>), true), ' |
| 'head(listPattern(varPattern(_, matchedType: int, ' |
| 'staticType: int), matchedType: List<int>, ' |
| 'requiredType: List<int>), true)), block()))'), |
| ]); |
| }); |
| }); |
| }); |
| |
| group('Refutability:', () { |
| test('When matched type is a subtype of variable type', () { |
| h.run([ |
| match(wildcard(type: 'num'), expr('int')) |
| .checkIr('match(expr(int), ' |
| 'varPattern(_, matchedType: int, staticType: num))'), |
| ]); |
| }); |
| |
| test('When matched type is dynamic', () { |
| h.run([ |
| match(wildcard(type: 'num'), expr('dynamic')) |
| .checkIr('match(expr(dynamic), ' |
| 'varPattern(_, matchedType: dynamic, staticType: num))'), |
| ]); |
| }); |
| |
| test('When matched type is not a subtype of variable type', () { |
| h.run([ |
| (match(wildcard(type: 'num')..errorId = 'PATTERN', expr('String')) |
| ..errorId = 'CONTEXT'), |
| ], expectedErrors: { |
| 'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, ' |
| 'context: CONTEXT, matchedType: String, requiredType: num)' |
| }); |
| }); |
| }); |
| }); |
| }); |
| } |