mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
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:
parent
6e03189e2b
commit
380a505b0d
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(' ');
|
||||
}
|
||||
|
|
|
@ -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)'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue