mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:37:53 +00:00
Shared type analysis: simplify handling of patterns.
Patterns may need to be visited twice during analysis: once to determine a type schema, and a second type to resolve the pattern match. Previously, the shared TypeAnalyzer had just a single `dispatchPattern` method, so it had to create temporary objects to record the structure of the patterns between the two visits. Now, there are two dispatch methods: `dispatchPatternSchema` and `dispatchPattern`. This avoids the creation of a bunch of temporary objects and makes the design much simpler. (Based on an idea from Brian Wilkerson) Change-Id: If10b6b7fb578594c3f660baa55d7e28123652638 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260282 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
1139ed3373
commit
8da75ede06
|
@ -106,40 +106,6 @@ class MatchContext<Node extends Object, Expression extends Node> {
|
||||||
topPattern: topPattern);
|
topPattern: topPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data structure returned by the [TypeAnalyzer] `analyze` methods for
|
|
||||||
/// patterns.
|
|
||||||
abstract class PatternDispatchResult<Node extends Object,
|
|
||||||
Expression extends Node, Variable extends Object, Type extends Object> {
|
|
||||||
/// The AST node for this pattern.
|
|
||||||
Node get node;
|
|
||||||
|
|
||||||
/// The type schema for this pattern.
|
|
||||||
Type get typeSchema;
|
|
||||||
|
|
||||||
/// Called by the [TypeAnalyzer] when an attempt is made to match this
|
|
||||||
/// pattern.
|
|
||||||
///
|
|
||||||
/// [matchedType] is the type of the thing being matched (for a variable
|
|
||||||
/// declaration, this is the type of the initializer or substructure thereof;
|
|
||||||
/// for a switch statement this is the type of the scrutinee or substructure
|
|
||||||
/// thereof).
|
|
||||||
///
|
|
||||||
/// [typeInfos] is a data structure keeping track of the variable patterns
|
|
||||||
/// seen so far and their type information.
|
|
||||||
///
|
|
||||||
/// [context] keeps track of other contextual information pertinent to the
|
|
||||||
/// match, such as whether it is late and/or final, whether there is an
|
|
||||||
/// initializer expression (and if so, what it is), and whether the match is
|
|
||||||
/// happening in an irrefutable context (and if so, what surrounding construct
|
|
||||||
/// causes it to be irrefutable).
|
|
||||||
///
|
|
||||||
/// Stack effect (see [TypeAnalyzer] for explanation): pushes (Pattern).
|
|
||||||
void match(
|
|
||||||
Type matchedType,
|
|
||||||
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
|
||||||
MatchContext<Node, Expression> context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Container for the result of running type analysis on an expression that does
|
/// Container for the result of running type analysis on an expression that does
|
||||||
/// not contain any null shorting.
|
/// not contain any null shorting.
|
||||||
class SimpleTypeAnalysisResult<Type extends Object>
|
class SimpleTypeAnalysisResult<Type extends Object>
|
||||||
|
|
|
@ -162,25 +162,79 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// Returns the unknown type context (`?`) used in type inference.
|
/// Returns the unknown type context (`?`) used in type inference.
|
||||||
Type get unknownType;
|
Type get unknownType;
|
||||||
|
|
||||||
/// Analyzes a cast pattern. [node] is the pattern itself, [innerPattern] is
|
/// Analyzes a cast pattern. [innerPattern] is the sub-pattern] and [type] is
|
||||||
/// the sub-pattern, and [type] is the type to cast to.
|
/// the type to cast to.
|
||||||
|
///
|
||||||
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
|
/// [context].
|
||||||
|
///
|
||||||
|
/// Stack effect: pushes (Pattern innerPattern).
|
||||||
|
void analyzeCastPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node innerPattern,
|
||||||
|
Type type) {
|
||||||
|
dispatchPattern(type, typeInfos, context, innerPattern);
|
||||||
|
// Stack: (Pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a cast pattern.
|
||||||
///
|
///
|
||||||
/// Stack effect: none.
|
/// Stack effect: none.
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type> analyzeCastPattern(
|
Type analyzeCastPatternSchema() => objectQuestionType;
|
||||||
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
|
/// Analyzes a constant pattern. [node] is the pattern itself, and
|
||||||
/// [expression] is the constant expression. Depending on the client's
|
/// [expression] is the constant expression. Depending on the client's
|
||||||
/// representation, [node] and [expression] might or might not be identical.
|
/// representation, [node] and [expression] might or might not be identical.
|
||||||
///
|
///
|
||||||
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
|
/// [context].
|
||||||
|
///
|
||||||
|
/// Stack effect: pushes (Expression).
|
||||||
|
void analyzeConstantPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node node,
|
||||||
|
Expression expression) {
|
||||||
|
// Stack: ()
|
||||||
|
TypeAnalyzerErrors<Node, Node, Expression, Variable, Type>? errors =
|
||||||
|
this.errors;
|
||||||
|
Node? irrefutableContext = context.irrefutableContext;
|
||||||
|
if (irrefutableContext != null) {
|
||||||
|
errors?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
||||||
|
}
|
||||||
|
Type staticType = analyzeExpression(expression, matchedType);
|
||||||
|
// Stack: (Expression)
|
||||||
|
if (errors != null && !options.patternsEnabled) {
|
||||||
|
Expression? switchScrutinee = context.getSwitchScrutinee(node);
|
||||||
|
if (switchScrutinee != null) {
|
||||||
|
bool nullSafetyEnabled = options.nullSafetyEnabled;
|
||||||
|
bool matches = nullSafetyEnabled
|
||||||
|
? typeOperations.isSubtypeOf(staticType, matchedType)
|
||||||
|
: typeOperations.isAssignableTo(staticType, matchedType);
|
||||||
|
if (!matches) {
|
||||||
|
errors.caseExpressionTypeMismatch(
|
||||||
|
caseExpression: expression,
|
||||||
|
scrutinee: switchScrutinee,
|
||||||
|
caseExpressionType: staticType,
|
||||||
|
scrutineeType: matchedType,
|
||||||
|
nullSafetyEnabled: nullSafetyEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a constant pattern.
|
||||||
|
///
|
||||||
/// Stack effect: none.
|
/// Stack effect: none.
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type>
|
Type analyzeConstantPatternSchema() {
|
||||||
analyzeConstantPattern(Node node, Expression expression) {
|
// Constant patterns are only allowed in refutable contexts, and refutable
|
||||||
return new _ConstantPatternDispatchResult<Node, Expression, Variable, Type>(
|
// contexts don't propagate a type schema into the scrutinee. So this
|
||||||
this, node, expression);
|
// code path is only reachable if the user's code contains errors.
|
||||||
|
errors?.assertInErrorRecovery();
|
||||||
|
return unknownType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyzes an expression. [node] is the expression to analyze, and
|
/// Analyzes an expression. [node] is the expression to analyze, and
|
||||||
|
@ -219,12 +273,10 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
flow?.ifStatement_conditionBegin();
|
flow?.ifStatement_conditionBegin();
|
||||||
Type initializerType = analyzeExpression(expression, unknownType);
|
Type initializerType = analyzeExpression(expression, unknownType);
|
||||||
// Stack: (Expression)
|
// Stack: (Expression)
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type>
|
|
||||||
patternDispatchResult = dispatchPattern(pattern);
|
|
||||||
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
||||||
// TODO(paulberry): rework handling of isFinal
|
// TODO(paulberry): rework handling of isFinal
|
||||||
patternDispatchResult.match(initializerType, typeInfos,
|
dispatchPattern(initializerType, typeInfos,
|
||||||
new MatchContext(isFinal: false, topPattern: pattern));
|
new MatchContext(isFinal: false, topPattern: pattern), pattern);
|
||||||
// Stack: (Expression, Pattern)
|
// Stack: (Expression, Pattern)
|
||||||
if (guard != null) {
|
if (guard != null) {
|
||||||
_checkGuardType(guard, analyzeExpression(guard, boolType));
|
_checkGuardType(guard, analyzeExpression(guard, boolType));
|
||||||
|
@ -273,24 +325,20 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
Node node, Node pattern, Expression initializer,
|
Node node, Node pattern, Expression initializer,
|
||||||
{required bool isFinal, required bool isLate}) {
|
{required bool isFinal, required bool isLate}) {
|
||||||
// Stack: ()
|
// Stack: ()
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type>
|
if (isLate && !isVariablePattern(pattern)) {
|
||||||
patternDispatchResult = dispatchPattern(pattern);
|
|
||||||
if (isLate &&
|
|
||||||
patternDispatchResult is! _VariablePatternDispatchResult<Object, Object,
|
|
||||||
Object, Object>) {
|
|
||||||
errors?.patternDoesNotAllowLate(pattern);
|
errors?.patternDoesNotAllowLate(pattern);
|
||||||
}
|
}
|
||||||
if (isLate) {
|
if (isLate) {
|
||||||
flow?.lateInitializer_begin(node);
|
flow?.lateInitializer_begin(node);
|
||||||
}
|
}
|
||||||
Type initializerType =
|
Type initializerType =
|
||||||
analyzeExpression(initializer, patternDispatchResult.typeSchema);
|
analyzeExpression(initializer, dispatchPatternSchema(pattern));
|
||||||
// Stack: (Expression)
|
// Stack: (Expression)
|
||||||
if (isLate) {
|
if (isLate) {
|
||||||
flow?.lateInitializer_end();
|
flow?.lateInitializer_end();
|
||||||
}
|
}
|
||||||
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
||||||
patternDispatchResult.match(
|
dispatchPattern(
|
||||||
initializerType,
|
initializerType,
|
||||||
typeInfos,
|
typeInfos,
|
||||||
new MatchContext(
|
new MatchContext(
|
||||||
|
@ -298,7 +346,8 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
isLate: isLate,
|
isLate: isLate,
|
||||||
initializer: initializer,
|
initializer: initializer,
|
||||||
irrefutableContext: node,
|
irrefutableContext: node,
|
||||||
topPattern: pattern));
|
topPattern: pattern),
|
||||||
|
pattern);
|
||||||
// Stack: (Expression, Pattern)
|
// Stack: (Expression, Pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,16 +366,61 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// the list element type (if explicitly supplied), and [elements] is the
|
/// the list element type (if explicitly supplied), and [elements] is the
|
||||||
/// list of subpatterns.
|
/// list of subpatterns.
|
||||||
///
|
///
|
||||||
/// Stack effect: none.
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type> analyzeListPattern(
|
/// [context].
|
||||||
|
///
|
||||||
|
/// Stack effect: pushes (n * Pattern) where n = elements.length.
|
||||||
|
Type analyzeListPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
Node node,
|
Node node,
|
||||||
{Type? elementType,
|
{Type? elementType,
|
||||||
required List<Node> elements}) {
|
required List<Node> elements}) {
|
||||||
return new _ListPatternDispatchResult<Node, Expression, Variable, Type>(
|
// Stack: ()
|
||||||
this,
|
Type? matchedElementType = typeOperations.matchListType(matchedType);
|
||||||
node,
|
if (matchedElementType == null) {
|
||||||
elementType,
|
if (typeOperations.isDynamic(matchedType)) {
|
||||||
[for (Node element in elements) dispatchPattern(element)]);
|
matchedElementType = dynamicType;
|
||||||
|
} else {
|
||||||
|
matchedElementType = objectQuestionType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Node element in elements) {
|
||||||
|
dispatchPattern(matchedElementType, typeInfos, context, element);
|
||||||
|
}
|
||||||
|
// Stack: (n * Pattern) where n = elements.length
|
||||||
|
Type requiredType = listType(elementType ?? matchedElementType);
|
||||||
|
Node? irrefutableContext = context.irrefutableContext;
|
||||||
|
if (irrefutableContext != null &&
|
||||||
|
!typeOperations.isAssignableTo(matchedType, requiredType)) {
|
||||||
|
errors?.patternTypeMismatchInIrrefutableContext(
|
||||||
|
pattern: node,
|
||||||
|
context: irrefutableContext,
|
||||||
|
matchedType: matchedType,
|
||||||
|
requiredType: requiredType);
|
||||||
|
}
|
||||||
|
return requiredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a list pattern. [elementType] is the list
|
||||||
|
/// element type (if explicitly supplied), and [elements] is the list of
|
||||||
|
/// subpatterns.
|
||||||
|
///
|
||||||
|
/// Stack effect: none.
|
||||||
|
Type analyzeListPatternSchema(
|
||||||
|
{Type? elementType, required List<Node> elements}) {
|
||||||
|
if (elementType == null) {
|
||||||
|
if (elements.isEmpty) {
|
||||||
|
return objectQuestionType;
|
||||||
|
}
|
||||||
|
elementType = dispatchPatternSchema(elements[0]);
|
||||||
|
for (int i = 1; i < elements.length; i++) {
|
||||||
|
elementType = typeOperations.glb(
|
||||||
|
elementType!, dispatchPatternSchema(elements[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listType(elementType!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyzes a logical-or or logical-and pattern. [node] is the pattern
|
/// Analyzes a logical-or or logical-and pattern. [node] is the pattern
|
||||||
|
@ -334,24 +428,94 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// operator. [isAnd] indicates whether [node] is a logical-or or a
|
/// operator. [isAnd] indicates whether [node] is a logical-or or a
|
||||||
/// logical-and.
|
/// logical-and.
|
||||||
///
|
///
|
||||||
/// Stack effect: none.
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type> analyzeLogicalPattern(
|
/// [context].
|
||||||
Node node, Node lhs, Node rhs,
|
///
|
||||||
|
/// Stack effect: pushes (Pattern left, Pattern right)
|
||||||
|
void analyzeLogicalPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node node,
|
||||||
|
Node lhs,
|
||||||
|
Node rhs,
|
||||||
{required bool isAnd}) {
|
{required bool isAnd}) {
|
||||||
return new _LogicalPatternDispatchResult<Node, Expression, Variable, Type>(
|
// Stack: ()
|
||||||
this, node, dispatchPattern(lhs), dispatchPattern(rhs), isAnd);
|
if (!isAnd) {
|
||||||
|
Node? irrefutableContext = context.irrefutableContext;
|
||||||
|
if (irrefutableContext != null) {
|
||||||
|
errors?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
||||||
|
// Avoid cascading errors
|
||||||
|
context = context.makeRefutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatchPattern(matchedType, typeInfos, context, lhs);
|
||||||
|
// Stack: (Pattern left)
|
||||||
|
dispatchPattern(matchedType, typeInfos, context, rhs);
|
||||||
|
// Stack: (Pattern left, Pattern right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a logical-or or logical-and pattern. [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.
|
||||||
|
Type analyzeLogicalPatternSchema(Node lhs, Node rhs, {required bool isAnd}) {
|
||||||
|
if (isAnd) {
|
||||||
|
return typeOperations.glb(
|
||||||
|
dispatchPatternSchema(lhs), dispatchPatternSchema(rhs));
|
||||||
|
} 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.
|
||||||
|
errors?.assertInErrorRecovery();
|
||||||
|
return unknownType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyzes a null-check or null-assert pattern. [node] is the pattern
|
/// Analyzes a null-check or null-assert pattern. [node] is the pattern
|
||||||
/// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates
|
/// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates
|
||||||
/// whether this is a null-check or a null-assert pattern.
|
/// whether this is a null-check or a null-assert pattern.
|
||||||
///
|
///
|
||||||
/// Stack effect: none.
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type>
|
/// [context].
|
||||||
analyzeNullCheckOrAssertPattern(Node node, Node innerPattern,
|
///
|
||||||
|
/// Stack effect: pushes (Pattern innerPattern).
|
||||||
|
void analyzeNullCheckOrAssertPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node node,
|
||||||
|
Node innerPattern,
|
||||||
{required bool isAssert}) {
|
{required bool isAssert}) {
|
||||||
return new _NullCheckOrAssertPatternDispatchResult(
|
// Stack: ()
|
||||||
this, node, dispatchPattern(innerPattern), isAssert);
|
Type innerMatchedType = typeOperations.promoteToNonNull(matchedType);
|
||||||
|
Node? irrefutableContext = context.irrefutableContext;
|
||||||
|
if (irrefutableContext != null && !isAssert) {
|
||||||
|
errors?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
||||||
|
// Avoid cascading errors
|
||||||
|
context = context.makeRefutable();
|
||||||
|
}
|
||||||
|
dispatchPattern(innerMatchedType, typeInfos, context, innerPattern);
|
||||||
|
// Stack: (Pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a null-check or null-assert pattern.
|
||||||
|
/// [innerPattern] is the sub-pattern and [isAssert] indicates whether this is
|
||||||
|
/// a null-check or a null-assert pattern.
|
||||||
|
///
|
||||||
|
/// Stack effect: none.
|
||||||
|
Type analyzeNullCheckOrAssertPatternSchema(Node innerPattern,
|
||||||
|
{required bool isAssert}) {
|
||||||
|
if (isAssert) {
|
||||||
|
return typeOperations.makeNullable(dispatchPatternSchema(innerPattern));
|
||||||
|
} 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.
|
||||||
|
errors?.assertInErrorRecovery();
|
||||||
|
return unknownType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analyzes an expression of the form `switch (expression) { cases }`.
|
/// Analyzes an expression of the form `switch (expression) { cases }`.
|
||||||
|
@ -374,13 +538,14 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos = {};
|
||||||
Node? pattern = memberInfo.head.pattern;
|
Node? pattern = memberInfo.head.pattern;
|
||||||
if (pattern != null) {
|
if (pattern != null) {
|
||||||
dispatchPattern(pattern).match(
|
dispatchPattern(
|
||||||
expressionType,
|
expressionType,
|
||||||
typeInfos,
|
typeInfos,
|
||||||
new MatchContext<Node, Expression>(
|
new MatchContext<Node, Expression>(
|
||||||
isFinal: false,
|
isFinal: false,
|
||||||
switchScrutinee: scrutinee,
|
switchScrutinee: scrutinee,
|
||||||
topPattern: pattern));
|
topPattern: pattern),
|
||||||
|
pattern);
|
||||||
// Stack: (Expression, i * ExpressionCase, Pattern)
|
// Stack: (Expression, i * ExpressionCase, Pattern)
|
||||||
Expression? guard = memberInfo.head.guard;
|
Expression? guard = memberInfo.head.guard;
|
||||||
bool hasGuard = guard != null;
|
bool hasGuard = guard != null;
|
||||||
|
@ -449,13 +614,14 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
CaseHeadOrDefaultInfo<Node, Expression> head = heads[j];
|
CaseHeadOrDefaultInfo<Node, Expression> head = heads[j];
|
||||||
Node? pattern = head.pattern;
|
Node? pattern = head.pattern;
|
||||||
if (pattern != null) {
|
if (pattern != null) {
|
||||||
dispatchPattern(pattern).match(
|
dispatchPattern(
|
||||||
scrutineeType,
|
scrutineeType,
|
||||||
typeInfos,
|
typeInfos,
|
||||||
new MatchContext<Node, Expression>(
|
new MatchContext<Node, Expression>(
|
||||||
isFinal: false,
|
isFinal: false,
|
||||||
switchScrutinee: scrutinee,
|
switchScrutinee: scrutinee,
|
||||||
topPattern: pattern));
|
topPattern: pattern),
|
||||||
|
pattern);
|
||||||
// Stack: (Expression, numExecutionPaths * StatementCase,
|
// Stack: (Expression, numExecutionPaths * StatementCase,
|
||||||
// numHeads * CaseHead, Pattern),
|
// numHeads * CaseHead, Pattern),
|
||||||
Expression? guard = head.guard;
|
Expression? guard = head.guard;
|
||||||
|
@ -543,16 +709,60 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// the variable, [declaredType] is the explicitly declared type (if present),
|
/// the variable, [declaredType] is the explicitly declared type (if present),
|
||||||
/// and [isFinal] indicates whether the variable is final.
|
/// and [isFinal] indicates whether the variable is final.
|
||||||
///
|
///
|
||||||
|
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
|
||||||
|
/// [context].
|
||||||
|
///
|
||||||
/// If this is a wildcard pattern (it doesn't bind any variable), [variable]
|
/// If this is a wildcard pattern (it doesn't bind any variable), [variable]
|
||||||
/// should be `null`.
|
/// should be `null`.
|
||||||
///
|
///
|
||||||
/// Stack effect: none.
|
/// Stack effect: none.
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type>
|
Type analyzeVariablePattern(
|
||||||
analyzeVariablePattern(Node node, Variable? variable, Type? declaredType,
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node node,
|
||||||
|
Variable? variable,
|
||||||
|
Type? declaredType,
|
||||||
{required bool isFinal}) {
|
{required bool isFinal}) {
|
||||||
return new _VariablePatternDispatchResult<Node, Expression, Variable, Type>(
|
Type staticType =
|
||||||
this, node, variable, declaredType, isFinal);
|
declaredType ?? variableTypeFromInitializerType(matchedType);
|
||||||
|
Node? irrefutableContext = context.irrefutableContext;
|
||||||
|
if (irrefutableContext != null &&
|
||||||
|
!typeOperations.isAssignableTo(matchedType, staticType)) {
|
||||||
|
errors?.patternTypeMismatchInIrrefutableContext(
|
||||||
|
pattern: node,
|
||||||
|
context: irrefutableContext,
|
||||||
|
matchedType: matchedType,
|
||||||
|
requiredType: staticType);
|
||||||
}
|
}
|
||||||
|
bool isImplicitlyTyped = declaredType == null;
|
||||||
|
if (variable != null) {
|
||||||
|
bool isFirstMatch = _recordTypeInfo(typeInfos,
|
||||||
|
pattern: node,
|
||||||
|
variable: variable,
|
||||||
|
staticType: staticType,
|
||||||
|
isImplicitlyTyped: isImplicitlyTyped);
|
||||||
|
if (isFirstMatch) {
|
||||||
|
flow?.declare(variable, false);
|
||||||
|
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?
|
||||||
|
flow?.initialize(variable, matchedType, context.getInitializer(node),
|
||||||
|
isFinal: context.isFinal || isFinal,
|
||||||
|
isLate: context.isLate,
|
||||||
|
isImplicitlyTyped: isImplicitlyTyped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return staticType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the type schema for a variable pattern. [declaredType] is the
|
||||||
|
/// explicitly declared type (if present).
|
||||||
|
///
|
||||||
|
/// Stack effect: none.
|
||||||
|
Type analyzeVariablePatternSchema(Type? declaredType) =>
|
||||||
|
declaredType ?? unknownType;
|
||||||
|
|
||||||
/// Calls the appropriate `analyze` method according to the form of
|
/// Calls the appropriate `analyze` method according to the form of
|
||||||
/// [expression], and then adjusts the stack as needed to combine any
|
/// [expression], and then adjusts the stack as needed to combine any
|
||||||
|
@ -567,9 +777,32 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
|
|
||||||
/// Calls the appropriate `analyze` method according to the form of [pattern].
|
/// Calls the appropriate `analyze` method according to the form of [pattern].
|
||||||
///
|
///
|
||||||
|
/// [matchedType] is the type of the thing being matched (for a variable
|
||||||
|
/// declaration, this is the type of the initializer or substructure thereof;
|
||||||
|
/// for a switch statement this is the type of the scrutinee or substructure
|
||||||
|
/// thereof).
|
||||||
|
///
|
||||||
|
/// [typeInfos] is a data structure keeping track of the variable patterns
|
||||||
|
/// seen so far and their type information.
|
||||||
|
///
|
||||||
|
/// [context] keeps track of other contextual information pertinent to the
|
||||||
|
/// match, such as whether it is late and/or final, whether there is an
|
||||||
|
/// initializer expression (and if so, what it is), and whether the match is
|
||||||
|
/// happening in an irrefutable context (and if so, what surrounding construct
|
||||||
|
/// causes it to be irrefutable).
|
||||||
|
///
|
||||||
|
/// Stack effect: pushes (Pattern).
|
||||||
|
void dispatchPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
|
Node node);
|
||||||
|
|
||||||
|
/// Calls the appropriate `analyze...Schema` method according to the form of
|
||||||
|
/// [pattern].
|
||||||
|
///
|
||||||
/// Stack effect: none.
|
/// Stack effect: none.
|
||||||
PatternDispatchResult<Node, Expression, Variable, Type> dispatchPattern(
|
Type dispatchPatternSchema(Node pattern);
|
||||||
Node pattern);
|
|
||||||
|
|
||||||
/// Calls the appropriate `analyze` method according to the form of
|
/// Calls the appropriate `analyze` method according to the form of
|
||||||
/// [statement], and then adjusts the stack as needed to combine any
|
/// [statement], and then adjusts the stack as needed to combine any
|
||||||
|
@ -629,22 +862,6 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
void handleCaseHead(Node node,
|
void handleCaseHead(Node node,
|
||||||
{required int caseIndex, required int subIndex});
|
{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
|
|
||||||
/// type of the expression being matched.
|
|
||||||
///
|
|
||||||
/// Stack effect: pops (Expression) and pushes (Pattern).
|
|
||||||
void handleConstantPattern(Node node, {required Type matchedType});
|
|
||||||
|
|
||||||
/// Called after visiting a `default` clause.
|
/// Called after visiting a `default` clause.
|
||||||
///
|
///
|
||||||
/// [node] is the enclosing switch statement or switch expression and
|
/// [node] is the enclosing switch statement or switch expression and
|
||||||
|
@ -653,27 +870,6 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// Stack effect: pushes (CaseHead).
|
/// Stack effect: pushes (CaseHead).
|
||||||
void handleDefault(Node node, int caseIndex);
|
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 after visiting a merged statement case.
|
/// Called after visiting a merged statement case.
|
||||||
///
|
///
|
||||||
/// [node] is enclosing switch statement, [caseIndex] is the index of the last
|
/// [node] is enclosing switch statement, [caseIndex] is the index of the last
|
||||||
|
@ -704,16 +900,6 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// Stack effect: pushes (Statement).
|
/// Stack effect: pushes (Statement).
|
||||||
void handleNoStatement(Statement node);
|
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
|
/// Called after visiting the scrutinee part of a switch statement or switch
|
||||||
/// expression. This is a hook to allow the client to start exhaustiveness
|
/// expression. This is a hook to allow the client to start exhaustiveness
|
||||||
/// analysis.
|
/// analysis.
|
||||||
|
@ -726,16 +912,6 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// Stack effect: none.
|
/// Stack effect: none.
|
||||||
void handleSwitchScrutinee(Type type);
|
void handleSwitchScrutinee(Type type);
|
||||||
|
|
||||||
/// Called when matching a variable pattern.
|
|
||||||
///
|
|
||||||
/// [node] is the AST node for the pattern, [matchedType] is the static type
|
|
||||||
/// of the expression being matched, and [staticType] is the static type of
|
|
||||||
/// the variable.
|
|
||||||
///
|
|
||||||
/// Stack effect: pushes (Pattern).
|
|
||||||
void handleVariablePattern(Node node,
|
|
||||||
{required Type matchedType, required Type staticType});
|
|
||||||
|
|
||||||
/// Queries whether the switch statement or expression represented by [node]
|
/// Queries whether the switch statement or expression represented by [node]
|
||||||
/// was exhaustive. [expressionType] is the static type of the scrutinee.
|
/// was exhaustive. [expressionType] is the static type of the scrutinee.
|
||||||
///
|
///
|
||||||
|
@ -743,6 +919,9 @@ mixin TypeAnalyzer<Node extends Object, Statement extends Node,
|
||||||
/// `default` clause.
|
/// `default` clause.
|
||||||
bool isSwitchExhaustive(Node node, Type expressionType);
|
bool isSwitchExhaustive(Node node, Type expressionType);
|
||||||
|
|
||||||
|
/// Queries whether [pattern] is a variable pattern.
|
||||||
|
bool isVariablePattern(Node pattern);
|
||||||
|
|
||||||
/// Returns the type `List`, with type parameter [elementType].
|
/// Returns the type `List`, with type parameter [elementType].
|
||||||
Type listType(Type elementType);
|
Type listType(Type elementType);
|
||||||
|
|
||||||
|
@ -956,332 +1135,3 @@ class VariableTypeInfo<Node extends Object, Type extends Object> {
|
||||||
/// The static type of this variable.
|
/// The static type of this variable.
|
||||||
Type get staticType => _latestStaticType;
|
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,
|
|
||||||
Expression extends Node, Variable extends Object, Type extends Object>
|
|
||||||
extends _PatternDispatchResultImpl<Node, Expression, Variable, Type> {
|
|
||||||
/// The constant or literal expression.
|
|
||||||
///
|
|
||||||
/// Depending on the client's representation, this might or might not be
|
|
||||||
/// identical to [node].
|
|
||||||
final Expression _expression;
|
|
||||||
|
|
||||||
_ConstantPatternDispatchResult(
|
|
||||||
super.typeAnalyzer, super.node, this._expression);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Type get typeSchema {
|
|
||||||
// Constant 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: ()
|
|
||||||
Node? irrefutableContext = context.irrefutableContext;
|
|
||||||
if (irrefutableContext != null) {
|
|
||||||
_typeAnalyzer.errors
|
|
||||||
?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
|
||||||
}
|
|
||||||
Type staticType = _typeAnalyzer.analyzeExpression(_expression, matchedType);
|
|
||||||
// Stack: (Expression)
|
|
||||||
TypeAnalyzerErrors<Node, Node, Expression, Variable, Type>? errors =
|
|
||||||
_typeAnalyzer.errors;
|
|
||||||
TypeAnalyzerOptions options = _typeAnalyzer.options;
|
|
||||||
if (errors != null && !options.patternsEnabled) {
|
|
||||||
Expression? switchScrutinee = context.getSwitchScrutinee(node);
|
|
||||||
if (switchScrutinee != null) {
|
|
||||||
TypeOperations2<Type> typeOperations = _typeAnalyzer.typeOperations;
|
|
||||||
bool nullSafetyEnabled = options.nullSafetyEnabled;
|
|
||||||
bool matches = nullSafetyEnabled
|
|
||||||
? typeOperations.isSubtypeOf(staticType, matchedType)
|
|
||||||
: typeOperations.isAssignableTo(staticType, matchedType);
|
|
||||||
if (!matches) {
|
|
||||||
errors.caseExpressionTypeMismatch(
|
|
||||||
caseExpression: _expression,
|
|
||||||
scrutinee: switchScrutinee,
|
|
||||||
caseExpressionType: staticType,
|
|
||||||
scrutineeType: matchedType,
|
|
||||||
nullSafetyEnabled: nullSafetyEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_typeAnalyzer.handleConstantPattern(node, matchedType: matchedType);
|
|
||||||
// Stack: (Pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
Expression extends Node, Variable extends Object, Type extends Object>
|
|
||||||
implements PatternDispatchResult<Node, Expression, Variable, Type> {
|
|
||||||
/// Pointer back to the [TypeAnalyzer].
|
|
||||||
final TypeAnalyzer<Node, Node, Expression, Variable, Type> _typeAnalyzer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final Node node;
|
|
||||||
|
|
||||||
_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 Type? _declaredType;
|
|
||||||
|
|
||||||
final bool _isFinal;
|
|
||||||
|
|
||||||
_VariablePatternDispatchResult(super._typeAnalyzer, super.node,
|
|
||||||
this._variable, this._declaredType, this._isFinal);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Type get typeSchema => _declaredType ?? _typeAnalyzer.unknownType;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void match(
|
|
||||||
Type matchedType,
|
|
||||||
Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
|
|
||||||
MatchContext<Node, Expression> context) {
|
|
||||||
// Stack: ()
|
|
||||||
Type staticType = _declaredType ??
|
|
||||||
_typeAnalyzer.variableTypeFromInitializerType(matchedType);
|
|
||||||
Node? irrefutableContext = context.irrefutableContext;
|
|
||||||
if (irrefutableContext != null &&
|
|
||||||
!_typeAnalyzer.typeOperations.isAssignableTo(matchedType, staticType)) {
|
|
||||||
_typeAnalyzer.errors?.patternTypeMismatchInIrrefutableContext(
|
|
||||||
pattern: node,
|
|
||||||
context: irrefutableContext,
|
|
||||||
matchedType: matchedType,
|
|
||||||
requiredType: staticType);
|
|
||||||
}
|
|
||||||
bool isImplicitlyTyped = _declaredType == null;
|
|
||||||
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);
|
|
||||||
// Stack: (Pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -968,6 +968,8 @@ abstract class Pattern extends Node with CaseHead, CaseHeads {
|
||||||
Pattern as_(String type) =>
|
Pattern as_(String type) =>
|
||||||
new _CastPattern(this, Type(type), location: computeLocation());
|
new _CastPattern(this, Type(type), location: computeLocation());
|
||||||
|
|
||||||
|
Type computeSchema(Harness h);
|
||||||
|
|
||||||
Pattern or(Pattern other) =>
|
Pattern or(Pattern other) =>
|
||||||
_LogicalPattern(this, other, isAnd: false, location: computeLocation());
|
_LogicalPattern(this, other, isAnd: false, location: computeLocation());
|
||||||
|
|
||||||
|
@ -977,7 +979,11 @@ abstract class Pattern extends Node with CaseHead, CaseHeads {
|
||||||
@override
|
@override
|
||||||
String toString() => _debugString(needsKeywordOrType: true);
|
String toString() => _debugString(needsKeywordOrType: true);
|
||||||
|
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h);
|
void visit(
|
||||||
|
Harness h,
|
||||||
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context);
|
||||||
|
|
||||||
CaseHead when(Expression guard) =>
|
CaseHead when(Expression guard) =>
|
||||||
_GuardedCaseHead(this, guard, location: location);
|
_GuardedCaseHead(this, guard, location: location);
|
||||||
|
@ -1245,6 +1251,8 @@ class _CastPattern extends Pattern {
|
||||||
|
|
||||||
_CastPattern(this._inner, this._type, {required super.location}) : super._();
|
_CastPattern(this._inner, this._type, {required super.location}) : super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) => h.typeAnalyzer.analyzeCastPatternSchema();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
|
@ -1252,8 +1260,18 @@ class _CastPattern extends Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
|
void visit(
|
||||||
return h.typeAnalyzer.analyzeCastPattern(this, _inner, _type);
|
Harness h,
|
||||||
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
h.typeAnalyzer
|
||||||
|
.analyzeCastPattern(matchedType, typeInfos, context, _inner, _type);
|
||||||
|
h.irBuilder.atom(_type.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.apply(
|
||||||
|
'castPattern', [Kind.pattern, Kind.type, Kind.type], Kind.pattern,
|
||||||
|
names: ['matchedType'], location: location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1513,15 +1531,26 @@ class _ConstantPattern extends Pattern {
|
||||||
|
|
||||||
_ConstantPattern(this.constant, {required super.location}) : super._();
|
_ConstantPattern(this.constant, {required super.location}) : super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) =>
|
||||||
|
h.typeAnalyzer.analyzeConstantPatternSchema();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
constant.preVisit(visitor);
|
constant.preVisit(visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void visit(
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) =>
|
Harness h,
|
||||||
h.typeAnalyzer.analyzeConstantPattern(this, constant);
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
h.typeAnalyzer.analyzeConstantPattern(
|
||||||
|
matchedType, typeInfos, context, this, constant);
|
||||||
|
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.apply('const', [Kind.expression, Kind.type], Kind.pattern,
|
||||||
|
names: ['matchedType'], location: location);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_debugString({required bool needsKeywordOrType}) => constant.toString();
|
_debugString({required bool needsKeywordOrType}) => constant.toString();
|
||||||
|
@ -2040,6 +2069,9 @@ class _ListPattern extends Pattern {
|
||||||
_ListPattern(this._elementType, this._elements, {required super.location})
|
_ListPattern(this._elementType, this._elements, {required super.location})
|
||||||
: super._();
|
: super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) => h.typeAnalyzer
|
||||||
|
.analyzeListPatternSchema(elementType: _elementType, elements: _elements);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
|
@ -2048,10 +2080,22 @@ class _ListPattern extends Pattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void visit(
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
|
Harness h,
|
||||||
return h.typeAnalyzer.analyzeListPattern(this,
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
var requiredType = h.typeAnalyzer.analyzeListPattern(
|
||||||
|
matchedType, typeInfos, context, this,
|
||||||
elementType: _elementType, elements: _elements);
|
elementType: _elementType, elements: _elements);
|
||||||
|
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.atom(requiredType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.apply(
|
||||||
|
'listPattern',
|
||||||
|
[...List.filled(_elements.length, Kind.pattern), Kind.type, Kind.type],
|
||||||
|
Kind.pattern,
|
||||||
|
names: ['matchedType', 'requiredType'],
|
||||||
|
location: location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2129,6 +2173,9 @@ class _LogicalPattern extends Pattern {
|
||||||
{required this.isAnd, required super.location})
|
{required this.isAnd, required super.location})
|
||||||
: super._();
|
: super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) =>
|
||||||
|
h.typeAnalyzer.analyzeLogicalPatternSchema(_lhs, _rhs, isAnd: isAnd);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
|
@ -2148,9 +2195,18 @@ class _LogicalPattern extends Pattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void visit(
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
|
Harness h,
|
||||||
return h.typeAnalyzer.analyzeLogicalPattern(this, _lhs, _rhs, isAnd: isAnd);
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
h.typeAnalyzer.analyzeLogicalPattern(
|
||||||
|
matchedType, typeInfos, context, this, _lhs, _rhs,
|
||||||
|
isAnd: isAnd);
|
||||||
|
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.apply(isAnd ? 'logicalAndPattern' : 'logicalOrPattern',
|
||||||
|
[Kind.pattern, Kind.pattern, Kind.type], Kind.pattern,
|
||||||
|
names: ['matchedType'], location: location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2588,9 +2644,17 @@ class _MiniAstTypeAnalyzer
|
||||||
_irBuilder.guard(expression, () => expression.visit(_harness, context));
|
_irBuilder.guard(expression, () => expression.visit(_harness, context));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> dispatchPattern(
|
void dispatchPattern(
|
||||||
|
Type matchedType,
|
||||||
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context,
|
||||||
covariant Pattern node) {
|
covariant Pattern node) {
|
||||||
return node.visit(_harness);
|
return node.visit(_harness, matchedType, typeInfos, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Type dispatchPatternSchema(covariant Pattern node) {
|
||||||
|
return node.computeSchema(_harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2644,50 +2708,11 @@ class _MiniAstTypeAnalyzer
|
||||||
location: node.location);
|
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);
|
|
||||||
_irBuilder.apply('const', [Kind.expression, Kind.type], Kind.pattern,
|
|
||||||
names: ['matchedType'], location: node.location);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleDefault(Node node, int caseIndex) {
|
void handleDefault(Node node, int caseIndex) {
|
||||||
_irBuilder.atom('default', Kind.caseHead, location: node.location);
|
_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleMergedStatementCase(Statement node,
|
void handleMergedStatementCase(Statement node,
|
||||||
{required int caseIndex,
|
{required int caseIndex,
|
||||||
|
@ -2723,19 +2748,9 @@ class _MiniAstTypeAnalyzer
|
||||||
_irBuilder.atom('noop', Kind.statement, location: node.location);
|
_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
|
@override
|
||||||
void handleSwitchScrutinee(Type type) {}
|
void handleSwitchScrutinee(Type type) {}
|
||||||
|
|
||||||
@override
|
|
||||||
void handleVariablePattern(covariant _VariablePattern node,
|
void handleVariablePattern(covariant _VariablePattern node,
|
||||||
{required Type matchedType, required Type staticType}) {
|
{required Type matchedType, required Type staticType}) {
|
||||||
_irBuilder.atom(node.variable?.name ?? '_', Kind.variable,
|
_irBuilder.atom(node.variable?.name ?? '_', Kind.variable,
|
||||||
|
@ -2757,6 +2772,9 @@ class _MiniAstTypeAnalyzer
|
||||||
return node.isExhaustive;
|
return node.isExhaustive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isVariablePattern(Node pattern) => pattern is _VariablePattern;
|
||||||
|
|
||||||
Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
|
Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2889,16 +2907,27 @@ class _NullCheckOrAssertPattern extends Pattern {
|
||||||
{required super.location})
|
{required super.location})
|
||||||
: super._();
|
: super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) => h.typeAnalyzer
|
||||||
|
.analyzeNullCheckOrAssertPatternSchema(_inner, isAssert: _isAssert);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
_inner.preVisit(visitor, variableBinder);
|
_inner.preVisit(visitor, variableBinder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void visit(
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
|
Harness h,
|
||||||
return h.typeAnalyzer
|
Type matchedType,
|
||||||
.analyzeNullCheckOrAssertPattern(this, _inner, isAssert: _isAssert);
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
h.typeAnalyzer.analyzeNullCheckOrAssertPattern(
|
||||||
|
matchedType, typeInfos, context, this, _inner,
|
||||||
|
isAssert: _isAssert);
|
||||||
|
h.irBuilder.atom(matchedType.type, Kind.type, location: location);
|
||||||
|
h.irBuilder.apply(_isAssert ? 'nullAssertPattern' : 'nullCheckPattern',
|
||||||
|
[Kind.pattern, Kind.type], Kind.pattern,
|
||||||
|
names: ['matchedType'], location: location);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -3301,6 +3330,9 @@ class _VariablePattern extends Pattern {
|
||||||
{this.isFinal = false, required super.location})
|
{this.isFinal = false, required super.location})
|
||||||
: super._();
|
: super._();
|
||||||
|
|
||||||
|
Type computeSchema(Harness h) =>
|
||||||
|
h.typeAnalyzer.analyzeVariablePatternSchema(declaredType);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void preVisit(
|
void preVisit(
|
||||||
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder) {
|
||||||
|
@ -3310,10 +3342,16 @@ class _VariablePattern extends Pattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void visit(
|
||||||
PatternDispatchResult<Node, Expression, Var, Type> visit(Harness h) {
|
Harness h,
|
||||||
return h.typeAnalyzer
|
Type matchedType,
|
||||||
.analyzeVariablePattern(this, variable, declaredType, isFinal: isFinal);
|
Map<Var, VariableTypeInfo<Node, Type>> typeInfos,
|
||||||
|
MatchContext<Node, Expression> context) {
|
||||||
|
var staticType = h.typeAnalyzer.analyzeVariablePattern(
|
||||||
|
matchedType, typeInfos, context, this, variable, declaredType,
|
||||||
|
isFinal: isFinal);
|
||||||
|
h.typeAnalyzer.handleVariablePattern(this,
|
||||||
|
matchedType: matchedType, staticType: staticType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
|
||||||
|
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
|
||||||
import 'package:analyzer/dart/analysis/features.dart';
|
import 'package:analyzer/dart/analysis/features.dart';
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/ast/precedence.dart';
|
import 'package:analyzer/dart/ast/precedence.dart';
|
||||||
|
@ -1111,6 +1113,19 @@ class BinaryPatternImpl extends DartPatternImpl implements BinaryPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitBinaryPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitBinaryPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
leftOperand.accept(visitor);
|
leftOperand.accept(visitor);
|
||||||
|
@ -1518,6 +1533,19 @@ class CastPatternImpl extends DartPatternImpl implements CastPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitCastPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitCastPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
type.accept(visitor);
|
type.accept(visitor);
|
||||||
|
@ -2730,6 +2758,19 @@ class ConstantPatternImpl extends DartPatternImpl implements ConstantPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitConstantPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitConstantPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
expression.accept(visitor);
|
expression.accept(visitor);
|
||||||
|
@ -3269,6 +3310,14 @@ abstract class DartPatternImpl extends AstNodeImpl implements DartPattern {
|
||||||
// TODO(brianwilkerson) Remove this and implement it in subclasses when we
|
// TODO(brianwilkerson) Remove this and implement it in subclasses when we
|
||||||
// have constants for pattern-related precedence values.
|
// have constants for pattern-related precedence values.
|
||||||
Precedence get precedence => throw UnimplementedError();
|
Precedence get precedence => throw UnimplementedError();
|
||||||
|
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor);
|
||||||
|
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that represents the declaration of one or more names. Each declared
|
/// A node that represents the declaration of one or more names. Each declared
|
||||||
|
@ -4636,6 +4685,19 @@ class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitExtractorPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitExtractorPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
typeName.accept(visitor);
|
typeName.accept(visitor);
|
||||||
|
@ -7971,6 +8033,19 @@ class ListPatternImpl extends DartPatternImpl implements ListPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitListPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitListPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
typeArguments?.accept(visitor);
|
typeArguments?.accept(visitor);
|
||||||
|
@ -8165,6 +8240,19 @@ class MapPatternImpl extends DartPatternImpl implements MapPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitMapPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitMapPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
typeArguments?.accept(visitor);
|
typeArguments?.accept(visitor);
|
||||||
|
@ -9383,6 +9471,19 @@ class ParenthesizedPatternImpl extends DartPatternImpl
|
||||||
E? accept<E>(AstVisitor<E> visitor) =>
|
E? accept<E>(AstVisitor<E> visitor) =>
|
||||||
visitor.visitParenthesizedPattern(this);
|
visitor.visitParenthesizedPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
pattern.accept(visitor);
|
pattern.accept(visitor);
|
||||||
|
@ -9814,6 +9915,19 @@ class PostfixPatternImpl extends DartPatternImpl implements PostfixPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitPostfixPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitPostfixPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
operand.accept(visitor);
|
operand.accept(visitor);
|
||||||
|
@ -10279,6 +10393,19 @@ class RecordPatternImpl extends DartPatternImpl implements RecordPattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitRecordPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitRecordPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
fields.accept(visitor);
|
fields.accept(visitor);
|
||||||
|
@ -10562,6 +10689,19 @@ class RelationalPatternImpl extends DartPatternImpl
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitRelationalPattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitRelationalPattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
operand.accept(visitor);
|
operand.accept(visitor);
|
||||||
|
@ -13237,6 +13377,19 @@ class VariablePatternImpl extends DartPatternImpl implements VariablePattern {
|
||||||
@override
|
@override
|
||||||
E? accept<E>(AstVisitor<E> visitor) => visitor.visitVariablePattern(this);
|
E? accept<E>(AstVisitor<E> visitor) => visitor.visitVariablePattern(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType computePatternSchema(ResolverVisitor resolverVisitor) =>
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolvePattern(
|
||||||
|
ResolverVisitor resolverVisitor,
|
||||||
|
DartType matchedType,
|
||||||
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
|
MatchContext<AstNode, Expression> context) {
|
||||||
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(AstVisitor visitor) {
|
void visitChildren(AstVisitor visitor) {
|
||||||
type?.accept(visitor);
|
type?.accept(visitor);
|
||||||
|
|
|
@ -707,15 +707,31 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PatternDispatchResult<AstNode, Expression, PromotableElement, DartType>
|
void dispatchPattern(
|
||||||
dispatchPattern(AstNode pattern) {
|
DartType matchedType,
|
||||||
if (pattern is Expression) {
|
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
|
||||||
return analyzeConstantPattern(pattern, pattern);
|
MatchContext<AstNode, Expression> context,
|
||||||
|
AstNode node) {
|
||||||
|
if (node is DartPatternImpl) {
|
||||||
|
node.resolvePattern(this, matchedType, typeInfos, context);
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError('TODO(paulberry): ${pattern.runtimeType}');
|
// This can occur inside conventional switch statements, since
|
||||||
|
// [SwitchCase] points directly to an [Expression] rather than to a
|
||||||
|
// [ConstantPattern]. So we mimic what
|
||||||
|
// [ConstantPatternImpl.resolvePattern] would do.
|
||||||
|
analyzeConstantPattern(
|
||||||
|
matchedType, typeInfos, context, node, node as Expression);
|
||||||
|
// Stack: (Expression)
|
||||||
|
popRewrite();
|
||||||
|
// Stack: ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DartType dispatchPatternSchema(covariant DartPatternImpl node) {
|
||||||
|
return node.computePatternSchema(this);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispatchStatement(Statement statement) {
|
void dispatchStatement(Statement statement) {
|
||||||
statement.accept(this);
|
statement.accept(this);
|
||||||
|
@ -782,36 +798,11 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||||
switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]);
|
switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void handleCastPattern(AstNode node,
|
|
||||||
{required DartType matchedType, DartType? staticType}) {
|
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void handleConstantPattern(AstNode node, {required DartType matchedType}) {
|
|
||||||
// Stack: (Expression)
|
|
||||||
popRewrite();
|
|
||||||
// Stack: ()
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleDefault(covariant SwitchStatement node, int caseIndex) {
|
void handleDefault(covariant SwitchStatement node, int caseIndex) {
|
||||||
switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]);
|
switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void handleListPattern(AstNode node, int numElements,
|
|
||||||
{required DartType matchedType, required DartType requiredType}) {
|
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void handleLogicalPattern(AstNode node,
|
|
||||||
{required bool isAnd, required DartType matchedType}) {
|
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleMergedStatementCase(covariant SwitchStatement node,
|
void handleMergedStatementCase(covariant SwitchStatement node,
|
||||||
{required int caseIndex,
|
{required int caseIndex,
|
||||||
|
@ -834,23 +825,11 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void handleNullCheckOrAssertPattern(AstNode node,
|
|
||||||
{required DartType matchedType, required bool isAssert}) {
|
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleSwitchScrutinee(DartType type) {
|
void handleSwitchScrutinee(DartType type) {
|
||||||
switchExhaustiveness = SwitchExhaustiveness(type);
|
switchExhaustiveness = SwitchExhaustiveness(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void handleVariablePattern(AstNode node,
|
|
||||||
{required DartType matchedType, DartType? staticType}) {
|
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If generic function instantiation should be performed on `expression`,
|
/// If generic function instantiation should be performed on `expression`,
|
||||||
/// inserts a [FunctionReference] node which wraps [expression].
|
/// inserts a [FunctionReference] node which wraps [expression].
|
||||||
///
|
///
|
||||||
|
@ -911,6 +890,9 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||||
bool isSwitchExhaustive(AstNode node, DartType expressionType) =>
|
bool isSwitchExhaustive(AstNode node, DartType expressionType) =>
|
||||||
switchExhaustiveness!.isExhaustive;
|
switchExhaustiveness!.isExhaustive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isVariablePattern(AstNode pattern) => pattern is VariablePattern;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DartType listType(DartType elementType) {
|
DartType listType(DartType elementType) {
|
||||||
throw UnimplementedError('TODO(paulberry)');
|
throw UnimplementedError('TODO(paulberry)');
|
||||||
|
|
|
@ -832,6 +832,7 @@ mb
|
||||||
mc
|
mc
|
||||||
md
|
md
|
||||||
me
|
me
|
||||||
|
meanings
|
||||||
meeting
|
meeting
|
||||||
merely
|
merely
|
||||||
meta
|
meta
|
||||||
|
|
Loading…
Reference in a new issue