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:
Paul Berry 2022-09-22 22:41:32 +00:00 committed by Commit Bot
parent 1139ed3373
commit 8da75ede06
6 changed files with 574 additions and 584 deletions

View file

@ -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>

View file

@ -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.
/// ///
/// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and
/// [context].
///
/// 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}) {
// Stack: ()
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. /// Stack effect: none.
PatternDispatchResult<Node, Expression, Variable, Type> Type analyzeNullCheckOrAssertPatternSchema(Node innerPattern,
analyzeNullCheckOrAssertPattern(Node node, Node innerPattern, {required bool isAssert}) {
{required bool isAssert}) { if (isAssert) {
return new _NullCheckOrAssertPatternDispatchResult( return typeOperations.makeNullable(dispatchPatternSchema(innerPattern));
this, node, dispatchPattern(innerPattern), isAssert); } 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,17 +709,61 @@ 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,
{required bool isFinal}) { Map<Variable, VariableTypeInfo<Node, Type>> typeInfos,
return new _VariablePatternDispatchResult<Node, Expression, Variable, Type>( MatchContext<Node, Expression> context,
this, node, variable, declaredType, isFinal); Node node,
Variable? variable,
Type? declaredType,
{required bool isFinal}) {
Type staticType =
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
/// sub-structures into a single expression. /// sub-structures into a single expression.
@ -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)
}
}

View file

@ -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

View file

@ -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);

View file

@ -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)');

View file

@ -832,6 +832,7 @@ mb
mc mc
md md
me me
meanings
meeting meeting
merely merely
meta meta