Shared type analysis: add more pattern types.

Support for the following pattern types is added to the (as yet
unused) shared type analysis prototype:

- Cast patterns
- List patterns
- Logical-and patterns
- Logical-or patterns
- Null-assert patterns
- Null-check patterns
- Wildcard patterns

Change-Id: I923df94b5deef925ca94e6ff0c8eac0493f69c1c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257602
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Paul Berry 2022-09-20 17:15:15 +00:00 committed by Commit Bot
parent 6e03189e2b
commit 380a505b0d
4 changed files with 1292 additions and 29 deletions

View file

@ -150,6 +150,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// Returns the type `int`.
Type get intType;
/// Returns the type `Object?`.
Type get objectQuestionType;
/// Options affecting the behavior of [TypeAnalyzer].
TypeAnalyzerOptions get options;
@ -159,6 +162,16 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// Returns the unknown type context (`?`) used in type inference.
Type get unknownType;
/// Analyzes a cast pattern. [node] is the pattern itself, [innerPattern] is
/// the sub-pattern, and [type] is the type to cast to.
///
/// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type> analyzeCastPattern(
Node node, Node innerPattern, Type type) {
return new _CastPatternDispatchResult<Node, Expression, Variable, Type>(
this, node, dispatchPattern(innerPattern), type);
}
/// Analyzes a constant pattern. [node] is the pattern itself, and
/// [expression] is the constant expression. Depending on the client's
/// representation, [node] and [expression] might or might not be identical.
@ -300,6 +313,47 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
type: type, convertedToDouble: convertToDouble);
}
/// Analyzes a list pattern. [node] is the pattern itself, [elementType] is
/// the list element type (if explicitly supplied), and [elements] is the
/// list of subpatterns.
///
/// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type> analyzeListPattern(
Node node,
{Type? elementType,
required List<Node> elements}) {
return new _ListPatternDispatchResult<Node, Expression, Variable, Type>(
this,
node,
elementType,
[for (Node element in elements) dispatchPattern(element)]);
}
/// Analyzes a logical-or or logical-and pattern. [node] is the pattern
/// itself, and [lhs] and [rhs] are the left and right sides of the `|` or `&`
/// operator. [isAnd] indicates whether [node] is a logical-or or a
/// logical-and.
///
/// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type> analyzeLogicalPattern(
Node node, Node lhs, Node rhs,
{required bool isAnd}) {
return new _LogicalPatternDispatchResult<Node, Expression, Variable, Type>(
this, node, dispatchPattern(lhs), dispatchPattern(rhs), isAnd);
}
/// Analyzes a null-check or null-assert pattern. [node] is the pattern
/// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates
/// whether this is a null-check or a null-assert pattern.
///
/// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type>
analyzeNullCheckOrAssertPattern(Node node, Node innerPattern,
{required bool isAssert}) {
return new _NullCheckOrAssertPatternDispatchResult(
this, node, dispatchPattern(innerPattern), isAssert);
}
/// Analyzes an expression of the form `switch (expression) { cases }`.
///
/// Stack effect: pushes (Expression, n * ExpressionCase), where n is the
@ -489,9 +543,12 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// the variable, [declaredType] is the explicitly declared type (if present),
/// and [isFinal] indicates whether the variable is final.
///
/// If this is a wildcard pattern (it doesn't bind any variable), [variable]
/// should be `null`.
///
/// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type>
analyzeVariablePattern(Node node, Variable variable, Type? declaredType,
analyzeVariablePattern(Node node, Variable? variable, Type? declaredType,
{required bool isFinal}) {
return new _VariablePatternDispatchResult<Node, Expression, Variable, Type>(
this, node, variable, declaredType, isFinal);
@ -585,6 +642,14 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
void handleCaseHead(Node node,
{required int caseIndex, required int subIndex});
/// Called when matching a cast pattern.
///
/// [node] is the AST node for the pattern and [matchedType] is the static
/// type of the expression being matched.
///
/// Stack effect: pushes (Pattern).
void handleCastPattern(Node node, {required Type matchedType});
/// Called when matching a constant pattern.
///
/// [node] is the AST node for the pattern and [matchedType] is the static
@ -601,6 +666,27 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// Stack effect: pushes (CaseHead).
void handleDefault(Node node, int caseIndex);
/// Called when matching a list pattern.
///
/// [node] is the AST node for the pattern, [numElements] is the number of
/// elements in the list pattern, [matchedType] is the static type of the
/// expression being matched, and [staticType] is the static type of the list
/// pattern.
///
/// Stack effect: pops (numElements * Pattern) and pushes (Pattern).
void handleListPattern(Node node, int numElements,
{required Type matchedType, required Type requiredType});
/// Called when matching a logical-and or logical-or pattern.
///
/// [node] is the AST node for the pattern, [isAnd] indicates whether it is a
/// logical-and or a logical-or pattern, and [matchedType] is the static type
/// of the expression being matched.
///
/// Stack effect: pops (Pattern left, Pattern right) and pushes (Pattern).
void handleLogicalPattern(Node node,
{required bool isAnd, required Type matchedType});
/// Called when visiting a `case` that lacks a guard clause. Since the lack
/// of a guard clause is semantically equivalent to `when true`, this method
/// should behave similarly to visiting the boolean literal `true`.
@ -618,6 +704,16 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// Stack effect: pushes (Statement).
void handleNoStatement(Statement node);
/// Called when matching a null-check or null-assert pattern.
///
/// [node] is the AST node for the pattern, [matchedType] is the static type
/// of the expression being matched, and [isAssert] indicates whether this is
/// a null-check or a null-assert pattern.
///
/// Stack effect: pops (Pattern) and pushes (Pattern).
void handleNullCheckOrAssertPattern(Node node,
{required Type matchedType, required bool isAssert});
/// Called after visiting the scrutinee part of a switch statement or switch
/// expression. This is a hook to allow the client to start exhaustiveness
/// analysis.
@ -647,6 +743,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
/// `default` clause.
bool isSwitchExhaustive(Node node, Type expressionType);
/// Returns the type `List`, with type parameter [elementType].
Type listType(Type elementType);
/// Records that type inference has assigned a [type] to a [variable]. This
/// is called once per variable, regardless of whether the variable's type is
/// explicit or inferred.
@ -858,6 +957,33 @@ class VariableTypeInfo<Node extends Object, Type extends Object> {
Type get staticType => _latestStaticType;
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeCastPattern]
class _CastPatternDispatchResult<Node extends Object, Expression extends Node,
Variable extends Object, Type extends Object>
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
final PatternDispatchResult<Node, Expression, Variable, Type> _innerPattern;
final Type _type;
_CastPatternDispatchResult(
super._typeAnalyzer, super.node, this._innerPattern, this._type);
@override
Type get typeSchema => _typeAnalyzer.objectQuestionType;
@override
void match(
Type matchedType,
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
MatchContext<Node, Expression> context) {
_innerPattern.match(_type, typeInfos, context);
// Stack: (Pattern)
_typeAnalyzer.handleCastPattern(node, matchedType: matchedType);
// Stack: (Pattern)
}
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeConstantPattern]
class _ConstantPatternDispatchResult<Node extends Object,
@ -920,6 +1046,171 @@ class _ConstantPatternDispatchResult<Node extends Object,
}
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeListPattern]
class _ListPatternDispatchResult<Node extends Object, Expression extends Node,
Variable extends Object, Type extends Object>
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
final Type? _elementType;
final List<PatternDispatchResult<Node, Expression, Variable, Type>> _elements;
_ListPatternDispatchResult(
super.typeAnalyzer, super._node, this._elementType, this._elements);
@override
Type get typeSchema {
Type? elementType = _elementType;
if (elementType == null) {
if (_elements.isEmpty) {
return _typeAnalyzer.objectQuestionType;
}
elementType = _elements[0].typeSchema;
for (int i = 1; i < _elements.length; i++) {
elementType = _typeAnalyzer.typeOperations
.glb(elementType!, _elements[i].typeSchema);
}
}
return _typeAnalyzer.listType(elementType!);
}
@override
void match(
Type matchedType,
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
MatchContext<Node, Expression> context) {
// Stack: ()
Type? elementType = _typeAnalyzer.typeOperations.matchListType(matchedType);
if (elementType == null) {
if (_typeAnalyzer.typeOperations.isDynamic(matchedType)) {
elementType = _typeAnalyzer.dynamicType;
} else {
elementType = _typeAnalyzer.objectQuestionType;
}
}
for (PatternDispatchResult<Node, Expression, Variable, Type> element
in _elements) {
element.match(elementType, typeInfos, context);
}
// Stack: (n * Pattern) where n = _elements.length
Type? requiredType = _typeAnalyzer.listType(_elementType ?? elementType);
Node? irrefutableContext = context.irrefutableContext;
if (irrefutableContext != null &&
!_typeAnalyzer.typeOperations
.isAssignableTo(matchedType, requiredType)) {
_typeAnalyzer.errors?.patternTypeMismatchInIrrefutableContext(
pattern: node,
context: irrefutableContext,
matchedType: matchedType,
requiredType: requiredType);
}
_typeAnalyzer.handleListPattern(node, _elements.length,
matchedType: matchedType, requiredType: requiredType);
// Stack: (Pattern)
}
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeLogicalPattern]
class _LogicalPatternDispatchResult<Node extends Object,
Expression extends Node, Variable extends Object, Type extends Object>
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
final PatternDispatchResult<Node, Expression, Variable, Type> _lhs;
final PatternDispatchResult<Node, Expression, Variable, Type> _rhs;
final bool _isAnd;
_LogicalPatternDispatchResult(
super._typeAnalyzer, super.node, this._lhs, this._rhs, this._isAnd);
@override
Type get typeSchema {
if (_isAnd) {
return _typeAnalyzer.typeOperations.glb(_lhs.typeSchema, _rhs.typeSchema);
} else {
// Logical-or patterns are only allowed in refutable contexts, and
// refutable contexts don't propagate a type schema into the scrutinee.
// So this code path is only reachable if the user's code contains errors.
_typeAnalyzer.errors?.assertInErrorRecovery();
return _typeAnalyzer.unknownType;
}
}
@override
void match(
Type matchedType,
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
MatchContext<Node, Expression> context) {
// Stack: ()
if (!_isAnd) {
Node? irrefutableContext = context.irrefutableContext;
if (irrefutableContext != null) {
_typeAnalyzer.errors
?.refutablePatternInIrrefutableContext(node, irrefutableContext);
// Avoid cascading errors
context = context.makeRefutable();
}
}
_lhs.match(matchedType, typeInfos, context);
// Stack: (Pattern left)
_rhs.match(matchedType, typeInfos, context);
// Stack: (Pattern left, Pattern right)
_typeAnalyzer.handleLogicalPattern(node,
isAnd: _isAnd, matchedType: matchedType);
// Stack: (Pattern)
}
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeNullCheckOrAssertPattern]
class _NullCheckOrAssertPatternDispatchResult<Node extends Object,
Expression extends Node, Variable extends Object, Type extends Object>
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
final PatternDispatchResult<Node, Expression, Variable, Type> _innerPattern;
final bool _isAssert;
_NullCheckOrAssertPatternDispatchResult(
super._typeAnalyzer, super.node, this._innerPattern, this._isAssert);
@override
Type get typeSchema {
if (_isAssert) {
return _typeAnalyzer.typeOperations
.makeNullable(_innerPattern.typeSchema);
} else {
// Null-check patterns are only allowed in refutable contexts, and
// refutable contexts don't propagate a type schema into the scrutinee.
// So this code path is only reachable if the user's code contains errors.
_typeAnalyzer.errors?.assertInErrorRecovery();
return _typeAnalyzer.unknownType;
}
}
@override
void match(
Type matchedType,
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
MatchContext<Node, Expression> context) {
// Stack: ()
Type innerMatchedType =
_typeAnalyzer.typeOperations.promoteToNonNull(matchedType);
Node? irrefutableContext = context.irrefutableContext;
if (irrefutableContext != null && !_isAssert) {
_typeAnalyzer.errors
?.refutablePatternInIrrefutableContext(node, irrefutableContext);
// Avoid cascading errors
context = context.makeRefutable();
}
_innerPattern.match(innerMatchedType, typeInfos, context);
// Stack: (Pattern)
_typeAnalyzer.handleNullCheckOrAssertPattern(node,
matchedType: matchedType, isAssert: _isAssert);
// Stack: (Pattern)
}
}
/// Common base class for all specializations of [PatternDispatchResult]
/// returned by methods in [TypeAnalyzer].
abstract class _PatternDispatchResultImpl<Node extends Object,
@ -934,10 +1225,12 @@ abstract class _PatternDispatchResultImpl<Node extends Object,
_PatternDispatchResultImpl(this._typeAnalyzer, this.node);
}
/// Specialization of [PatternDispatchResult] returned by
/// [TypeAnalyzer.analyzeVariablePattern]
class _VariablePatternDispatchResult<Node extends Object,
Expression extends Node, Variable extends Object, Type extends Object>
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
final Variable _variable;
final Variable? _variable;
final Type? _declaredType;
@ -967,22 +1260,25 @@ class _VariablePatternDispatchResult<Node extends Object,
requiredType: staticType);
}
bool isImplicitlyTyped = _declaredType == null;
bool isFirstMatch = _typeAnalyzer._recordTypeInfo(typeInfos,
pattern: node,
variable: _variable,
staticType: staticType,
isImplicitlyTyped: isImplicitlyTyped);
if (isFirstMatch) {
_typeAnalyzer.flow?.declare(_variable, false);
_typeAnalyzer.setVariableType(_variable, staticType);
// TODO(paulberry): are we handling _isFinal correctly?
// TODO(paulberry): do we need to verify that all instances of a
// variable are final or all are not final?
_typeAnalyzer.flow?.initialize(
_variable, matchedType, context.getInitializer(node),
isFinal: context.isFinal || _isFinal,
isLate: context.isLate,
Variable? variable = _variable;
if (variable != null) {
bool isFirstMatch = _typeAnalyzer._recordTypeInfo(typeInfos,
pattern: node,
variable: variable,
staticType: staticType,
isImplicitlyTyped: isImplicitlyTyped);
if (isFirstMatch) {
_typeAnalyzer.flow?.declare(variable, false);
_typeAnalyzer.setVariableType(variable, staticType);
// TODO(paulberry): are we handling _isFinal correctly?
// TODO(paulberry): do we need to verify that all instances of a
// variable are final or all are not final?
_typeAnalyzer.flow?.initialize(
variable, matchedType, context.getInitializer(node),
isFinal: context.isFinal || _isFinal,
isLate: context.isLate,
isImplicitlyTyped: isImplicitlyTyped);
}
}
_typeAnalyzer.handleVariablePattern(node,
matchedType: matchedType, staticType: staticType);

View file

@ -84,6 +84,9 @@ mixin TypeOperations<Type extends Object> {
/// TODO(paulberry): once the analyzer and front end both use [TypeAnalyzer],
/// combine this mixin with [TypeOperations].
mixin TypeOperations2<Type extends Object> implements TypeOperations<Type> {
/// Computes the greatest lower bound of [type1] and [type2].
Type glb(Type type1, Type type2);
/// Returns `true` if [fromType] is assignable to [toType].
bool isAssignableTo(Type fromType, Type toType);
@ -92,4 +95,12 @@ mixin TypeOperations2<Type extends Object> implements TypeOperations<Type> {
/// Computes the least upper bound of [type1] and [type2].
Type lub(Type type1, Type type2);
/// Computes the nullable form of [type], in other words the least upper bound
/// of [type] and `Null`.
Type makeNullable(Type type);
/// If [type] is a subtype of the type `List<T>` for some `T`, returns the
/// type `T`. Otherwise returns `null`.
Type? matchListType(Type type);
}

View file

@ -204,6 +204,10 @@ Expression intLiteral(int value, {bool? expectConversionToDouble}) =>
expectConversionToDouble: expectConversionToDouble,
location: computeLocation());
Pattern listPattern(List<Pattern> elements, {String? elementType}) =>
_ListPattern(elementType == null ? null : Type(elementType), elements,
location: computeLocation());
Statement localFunction(List<Statement> body) {
var location = computeLocation();
return _LocalFunction(_Block(body, location: location), location: location);
@ -252,6 +256,11 @@ Statement while_(Expression condition, List<Statement> body) {
location: location);
}
Pattern wildcard(
{String? type, String? expectInferredType, bool isFinal = false}) =>
_VariablePattern(type == null ? null : Type(type), null, expectInferredType,
isFinal: isFinal, location: computeLocation());
mixin CaseHead implements CaseHeads, Node {
@override
List<CaseHead> get _caseHeads => [this];
@ -449,6 +458,8 @@ class Harness
'double <: int?': false,
'double <: String': false,
'dynamic <: int': false,
'dynamic <: Null': false,
'dynamic <: Object': false,
'int <: bool': false,
'int <: double': false,
'int <: double?': false,
@ -472,11 +483,14 @@ class Harness
'int? <: num?': true,
'int? <: Object': false,
'int? <: Object?': true,
'List<int> <: Object': true,
'Never <: Object?': true,
'Null <: double?': true,
'Null <: int': false,
'Null <: Object': false,
'Null <: Object?': true,
'Null <: dynamic': true,
'num <: double': false,
'num <: int': false,
'num <: Iterable': false,
'num <: List': false,
@ -500,6 +514,7 @@ class Harness
'List <: int': false,
'List <: Iterable': true,
'List <: Object': true,
'List<int> <: List<num>': true,
'Never <: int': true,
'Never <: int?': true,
'Never <: Null': true,
@ -511,6 +526,7 @@ class Harness
'Object <: int': false,
'Object <: int?': false,
'Object <: List': false,
'Object <: List<Object?>': false,
'Object <: Null': false,
'Object <: num': false,
'Object <: num?': false,
@ -522,6 +538,7 @@ class Harness
'Object? <: Null': false,
'String <: int': false,
'String <: int?': false,
'String <: List<num>': false,
'String <: num': false,
'String <: num?': false,
'String <: Object': true,
@ -568,9 +585,21 @@ class Harness
'num* - Object': Type('Never'),
};
static final Map<String, Type> _coreGlbs = {
'double, int': Type('Never'),
'double?, int?': Type('Null'),
'int?, num': Type('int'),
};
static final Map<String, Type> _coreLubs = {
'double, int': Type('num'),
'double?, int?': Type('num?'),
'int, num': Type('num'),
'Never, int': Type('int'),
'Null, int': Type('int?'),
'?, int': Type('int'),
'?, List<?>': Type('List<?>'),
'?, Null': Type('Null'),
};
bool _started = false;
@ -587,6 +616,8 @@ class Harness
final Map<String, Type> _factorResults = Map.of(_coreFactors);
final Map<String, Type> _glbs = Map.of(_coreGlbs);
final Map<String, Type> _lubs = Map.of(_coreLubs);
final Map<String, _PropertyElement> _members = {};
@ -688,6 +719,15 @@ class Harness
return _members[query] ?? fail('Unknown member query: $query');
}
@override
Type glb(Type type1, Type type2) {
if (type1.type == type2.type) return type1;
var typeNames = [type1.type, type2.type];
typeNames.sort();
var query = typeNames.join(', ');
return _glbs[query] ?? fail('Unknown glb query: $query');
}
@override
bool isAssignableTo(Type fromType, Type toType) {
if (legacy && isSubtypeOf(toType, fromType)) return true;
@ -728,6 +768,19 @@ class Harness
return _lubs[query] ?? fail('Unknown lub query: $query');
}
@override
Type makeNullable(Type type) => lub(type, Type('Null'));
@override
Type? matchListType(Type type) {
if (type is NonFunctionType) {
if (type.args.length == 1) {
return type.args[0];
}
}
return null;
}
@override
Type promoteToNonNull(Type type) {
if (type.type.endsWith('?')) {
@ -894,12 +947,27 @@ class Node {
abstract class Pattern extends Node with CaseHead, CaseHeads {
Pattern._({required super.location}) : super._();
Pattern get nullAssert =>
_NullCheckOrAssertPattern(this, true, location: computeLocation());
Pattern get nullCheck =>
_NullCheckOrAssertPattern(this, false, location: computeLocation());
@override
Expression? get _guard => null;
@override
Pattern? get _pattern => this;
Pattern and(Pattern other) =>
_LogicalPattern(this, other, isAnd: true, location: computeLocation());
Pattern as_(String type) =>
new _CastPattern(this, Type(type), location: computeLocation());
Pattern or(Pattern other) =>
_LogicalPattern(this, other, isAnd: false, location: computeLocation());
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder);
@ -1167,6 +1235,30 @@ class _CaseHeads with CaseHeads {
_CaseHeads(this._caseHeads, this._labels);
}
class _CastPattern extends Pattern {
final Pattern _inner;
final Type _type;
_CastPattern(this._inner, this._type, {required super.location}) : super._();
@override
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
_inner.preVisit(visitor, variableBinder);
}
@override
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
return h.typeAnalyzer.analyzeCastPattern(this, _inner, _type);
}
@override
String _debugString({required bool needsKeywordOrType}) =>
'${_inner._debugString(needsKeywordOrType: needsKeywordOrType)} as '
'${_type.type}';
}
/// Representation of a single catch clause in a try/catch statement. Use
/// [catch_] to create instances of this class.
class _CatchClause {
@ -1490,7 +1582,7 @@ class _Declare extends Statement {
if (initializer == null) {
var pattern = this.pattern as _VariablePattern;
var staticType = h.typeAnalyzer.analyzeUninitializedVariableDeclaration(
this, pattern.variable, pattern.declaredType,
this, pattern.variable!, pattern.declaredType,
isFinal: isFinal, isLate: isLate);
h.typeAnalyzer.handleVariablePattern(pattern,
matchedType: staticType, staticType: staticType);
@ -1937,6 +2029,38 @@ class _LabeledStatement extends Statement {
}
}
class _ListPattern extends Pattern {
final Type? _elementType;
final List<Pattern> _elements;
_ListPattern(this._elementType, this._elements, {required super.location})
: super._();
@override
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
for (var element in _elements) {
element.preVisit(visitor, variableBinder);
}
}
@override
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
return h.typeAnalyzer.analyzeListPattern(this,
elementType: _elementType, elements: _elements);
}
@override
String _debugString({required bool needsKeywordOrType}) {
var elements = [
for (var element in _elements)
element._debugString(needsKeywordOrType: needsKeywordOrType)
];
return '[${elements.join(', ')}]';
}
}
class _LocalFunction extends Statement {
final Statement body;
@ -1991,6 +2115,49 @@ class _Logical extends Expression {
}
}
class _LogicalPattern extends Pattern {
final Pattern _lhs;
final Pattern _rhs;
final bool isAnd;
_LogicalPattern(this._lhs, this._rhs,
{required this.isAnd, required super.location})
: super._();
@override
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
if (!isAnd) {
variableBinder.startAlternatives();
variableBinder.startAlternative(_lhs);
}
_lhs.preVisit(visitor, variableBinder);
if (!isAnd) {
variableBinder.finishAlternative();
variableBinder.startAlternative(_rhs);
}
_rhs.preVisit(visitor, variableBinder);
if (!isAnd) {
variableBinder.finishAlternative();
variableBinder.finishAlternatives();
}
}
@override
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
return h.typeAnalyzer.analyzeLogicalPattern(this, _lhs, _rhs, isAnd: isAnd);
}
@override
_debugString({required bool needsKeywordOrType}) => [
_lhs._debugString(needsKeywordOrType: false),
isAnd ? '&' : '|',
_rhs._debugString(needsKeywordOrType: false)
].join(' ');
}
/// Enum representing the different ways an [LValue] might be used.
enum _LValueDisposition {
/// The [LValue] is being read from only, not written to. This happens if it
@ -2143,6 +2310,9 @@ class _MiniAstTypeAnalyzer
late final Type nullType = Type('Null');
@override
late final Type objectQuestionType = Type('Object?');
@override
late final Type unknownType = Type('?');
@ -2484,6 +2654,16 @@ class _MiniAstTypeAnalyzer
location: node.location);
}
@override
void handleCastPattern(covariant _CastPattern node,
{required Type matchedType}) {
_irBuilder.atom(node._type.type, Kind.type, location: node.location);
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
_irBuilder.apply(
'castPattern', [Kind.pattern, Kind.type, Kind.type], Kind.pattern,
names: ['matchedType'], location: node.location);
}
@override
void handleConstantPattern(Node node, {required Type matchedType}) {
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
@ -2496,6 +2676,28 @@ class _MiniAstTypeAnalyzer
_irBuilder.atom('default', Kind.caseHead, location: node.location);
}
@override
void handleListPattern(Node node, int numElements,
{required Type matchedType, required Type requiredType}) {
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
_irBuilder.atom(requiredType.type, Kind.type, location: node.location);
_irBuilder.apply(
'listPattern',
[...List.filled(numElements, Kind.pattern), Kind.type, Kind.type],
Kind.pattern,
names: ['matchedType', 'requiredType'],
location: node.location);
}
@override
void handleLogicalPattern(Node node,
{required bool isAnd, required Type matchedType}) {
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
_irBuilder.apply(isAnd ? 'logicalAndPattern' : 'logicalOrPattern',
[Kind.pattern, Kind.pattern, Kind.type], Kind.pattern,
names: ['matchedType'], location: node.location);
}
void handleNoCondition(Node node) {
_irBuilder.atom('true', Kind.expression, location: node.location);
}
@ -2518,13 +2720,23 @@ class _MiniAstTypeAnalyzer
_irBuilder.atom('noop', Kind.statement, location: node.location);
}
@override
void handleNullCheckOrAssertPattern(Node node,
{required Type matchedType, required bool isAssert}) {
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
_irBuilder.apply(isAssert ? 'nullAssertPattern' : 'nullCheckPattern',
[Kind.pattern, Kind.type], Kind.pattern,
names: ['matchedType'], location: node.location);
}
@override
void handleSwitchScrutinee(Type type) {}
@override
void handleVariablePattern(covariant _VariablePattern node,
{required Type matchedType, required Type staticType}) {
_irBuilder.atom(node.variable.name, Kind.variable, location: node.location);
_irBuilder.atom(node.variable?.name ?? '_', Kind.variable,
location: node.location);
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
_irBuilder.atom(staticType.type, Kind.type, location: node.location);
_irBuilder.apply(
@ -2544,6 +2756,10 @@ class _MiniAstTypeAnalyzer
Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
@override
Type listType(Type elementType) =>
NonFunctionType('List', args: [elementType]);
_PropertyElement lookupInterfaceMember(
Node node, Type receiverType, String memberName) {
return _harness.getMember(receiverType, memberName);
@ -2661,6 +2877,32 @@ class _NullAwareAccess extends Expression {
}
}
class _NullCheckOrAssertPattern extends Pattern {
final Pattern _inner;
final bool _isAssert;
_NullCheckOrAssertPattern(this._inner, this._isAssert,
{required super.location})
: super._();
@override
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
_inner.preVisit(visitor, variableBinder);
}
@override
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
return h.typeAnalyzer
.analyzeNullCheckOrAssertPattern(this, _inner, isAssert: _isAssert);
}
@override
String _debugString({required bool needsKeywordOrType}) =>
'${_inner._debugString(needsKeywordOrType: needsKeywordOrType)}?';
}
class _NullLiteral extends Expression {
_NullLiteral({required super.location});
@ -3046,7 +3288,7 @@ class _TryStatement extends TryStatement {
class _VariablePattern extends Pattern {
final Type? declaredType;
final Var variable;
final Var? variable;
final String? expectInferredType;
@ -3059,7 +3301,8 @@ class _VariablePattern extends Pattern {
@override
void preVisit(
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
if (variableBinder.add(this, variable)) {
var variable = this.variable;
if (variable != null && variableBinder.add(this, variable)) {
visitor._assignedVariables.declare(variable);
}
}
@ -3076,7 +3319,7 @@ class _VariablePattern extends Pattern {
declaredType!.type
else if (needsKeywordOrType)
'var',
variable.name,
variable?.name ?? '_',
if (expectInferredType != null) '(expected type $expectInferredType)'
].join(' ');
}

View file

@ -605,7 +605,21 @@ main() {
});
test('implicit/implicit type', () {
// TODO(paulberry): need more support to be able to test this
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>)'
});
});
});
@ -1097,16 +1111,13 @@ main() {
});
test('illegal late pattern', () {
// TODO(paulberry): once we support some kind of irrefutable pattern
// other than a variable declaration, adjust this test so that the only
// error it expects is `patternDoesNotAllowLate`.
h.run([
(match(intLiteral(1).pattern..errorId = 'PATTERN', intLiteral(0),
(match(
listPattern([wildcard()])..errorId = 'PATTERN', expr('List<int>'),
isLate: true)
..errorId = 'CONTEXT'),
], expectedErrors: {
'patternDoesNotAllowLate(PATTERN)',
'refutablePatternInIrrefutableContext(PATTERN, CONTEXT)'
'patternDoesNotAllowLate(PATTERN)'
});
});
@ -1122,6 +1133,119 @@ main() {
});
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([
@ -1133,6 +1257,431 @@ main() {
});
});
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('Variable:', () {
group('Refutability:', () {
test('When matched type is a subtype of variable type', () {
@ -1165,5 +1714,169 @@ main() {
});
});
});
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)'
});
});
});
});
});
}