mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 14:39:38 +00:00
Shared pattern analysis: add support for pattern variable assignment.
Bug: https://github.com/dart-lang/sdk/issues/50585 Change-Id: I1939c6fd8fac205abeac5b0a9b3da9b3b4adca01 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274604 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
9f2a622d79
commit
966aed6bd1
|
@ -187,6 +187,14 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// A function parameter is always initialized, so [initialized] is `true`.
|
||||
void declare(Variable variable, bool initialized);
|
||||
|
||||
/// Call this method after visiting a variable pattern in a non-assignment
|
||||
/// context (or a wildcard pattern).
|
||||
///
|
||||
/// [matchedType] should be the static type of the value being matched.
|
||||
/// [staticType] should be the static type of the variable pattern itself.
|
||||
void declaredVariablePattern(
|
||||
{required Type matchedType, required Type staticType});
|
||||
|
||||
/// Call this method before visiting the body of a "do-while" statement.
|
||||
/// [doStatement] should be the same node that was passed to
|
||||
/// [AssignedVariables.endNode] for the do-while statement.
|
||||
|
@ -518,6 +526,15 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
void parenthesizedExpression(
|
||||
Expression outerExpression, Expression innerExpression);
|
||||
|
||||
/// Call this method just after visiting the right hand side of a pattern
|
||||
/// assignment expression, and before visiting the pattern.
|
||||
///
|
||||
/// [rhs] is the right hand side expression.
|
||||
void patternAssignment_afterRhs(Expression rhs);
|
||||
|
||||
/// Call this method after visiting a pattern assignment expression.
|
||||
void patternAssignment_end();
|
||||
|
||||
/// Call this method just after visiting the initializer of a pattern variable
|
||||
/// declaration, and before visiting the pattern.
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer);
|
||||
|
@ -746,12 +763,6 @@ abstract class FlowAnalysis<Node extends Object, Statement extends Node,
|
|||
/// statement.
|
||||
void tryFinallyStatement_finallyBegin(Node body);
|
||||
|
||||
/// Call this method after visiting a variable pattern.
|
||||
///
|
||||
/// [matchedType] should be the static type of the value being matched.
|
||||
/// [staticType] should be the static type of the variable pattern itself.
|
||||
void variablePattern({required Type matchedType, required Type staticType});
|
||||
|
||||
/// Call this method when encountering an expression that reads the value of
|
||||
/// a variable.
|
||||
///
|
||||
|
@ -950,6 +961,16 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.declare(variable, initialized));
|
||||
}
|
||||
|
||||
@override
|
||||
void declaredVariablePattern(
|
||||
{required Type matchedType, required Type staticType}) {
|
||||
_wrap(
|
||||
'declaredVariablePattern(matchedType: $matchedType, '
|
||||
'staticType: $staticType)',
|
||||
() => _wrapped.declaredVariablePattern(
|
||||
matchedType: matchedType, staticType: staticType));
|
||||
}
|
||||
|
||||
@override
|
||||
void doStatement_bodyBegin(Statement doStatement) {
|
||||
return _wrap('doStatement_bodyBegin($doStatement)',
|
||||
|
@ -1243,6 +1264,17 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
_wrapped.parenthesizedExpression(outerExpression, innerExpression));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternAssignment_afterRhs(Expression rhs) {
|
||||
_wrap('patternAssignment_afterRhs($rhs)',
|
||||
() => _wrapped.patternAssignment_afterRhs(rhs));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternAssignment_end() {
|
||||
_wrap('patternAssignment_end()', () => _wrapped.patternAssignment_end());
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {
|
||||
_wrap(
|
||||
|
@ -1405,14 +1437,6 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
|
|||
() => _wrapped.tryFinallyStatement_finallyBegin(body));
|
||||
}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {
|
||||
_wrap(
|
||||
'variablePattern(matchedType: $matchedType, staticType: $staticType)',
|
||||
() => _wrapped.variablePattern(
|
||||
matchedType: matchedType, staticType: staticType));
|
||||
}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
return _wrap('variableRead($expression, $variable)',
|
||||
|
@ -3371,6 +3395,25 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
promotionKeyStore.keyForVariable(variable), initialized);
|
||||
}
|
||||
|
||||
@override
|
||||
void declaredVariablePattern(
|
||||
{required Type matchedType, required Type staticType}) {
|
||||
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
|
||||
ReferenceWithType<Type>? scrutineeReference = context._scrutineeReference;
|
||||
bool coversMatchedType =
|
||||
typeOperations.isSubtypeOf(matchedType, staticType);
|
||||
if (scrutineeReference != null) {
|
||||
ExpressionInfo<Type> promotionInfo =
|
||||
_current.tryPromoteForTypeCheck(this, scrutineeReference, staticType);
|
||||
_current = promotionInfo.ifTrue;
|
||||
if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, promotionInfo.ifFalse);
|
||||
}
|
||||
} else if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, _current);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void doStatement_bodyBegin(Statement doStatement) {
|
||||
AssignedVariablesNodeInfo info =
|
||||
|
@ -3848,6 +3891,16 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
forwardExpression(outerExpression, innerExpression);
|
||||
}
|
||||
|
||||
@override
|
||||
void patternAssignment_afterRhs(Expression rhs) {
|
||||
_pushPattern(_getExpressionReference(rhs));
|
||||
}
|
||||
|
||||
@override
|
||||
void patternAssignment_end() {
|
||||
_popPattern(null);
|
||||
}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {
|
||||
_pushPattern(_getExpressionReference(initializer));
|
||||
|
@ -4048,24 +4101,6 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
|
|||
context._beforeFinally = _current;
|
||||
}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {
|
||||
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
|
||||
ReferenceWithType<Type>? scrutineeReference = context._scrutineeReference;
|
||||
bool coversMatchedType =
|
||||
typeOperations.isSubtypeOf(matchedType, staticType);
|
||||
if (scrutineeReference != null) {
|
||||
ExpressionInfo<Type> promotionInfo =
|
||||
_current.tryPromoteForTypeCheck(this, scrutineeReference, staticType);
|
||||
_current = promotionInfo.ifTrue;
|
||||
if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, promotionInfo.ifFalse);
|
||||
}
|
||||
} else if (!coversMatchedType) {
|
||||
context._unmatched = _join(context._unmatched, _current);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
int variableKey = promotionKeyStore.keyForVariable(variable);
|
||||
|
@ -4510,6 +4545,10 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
@override
|
||||
void declare(Variable variable, bool initialized) {}
|
||||
|
||||
@override
|
||||
void declaredVariablePattern(
|
||||
{required Type matchedType, required Type staticType}) {}
|
||||
|
||||
@override
|
||||
void doStatement_bodyBegin(Statement doStatement) {}
|
||||
|
||||
|
@ -4778,6 +4817,12 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
forwardExpression(outerExpression, innerExpression);
|
||||
}
|
||||
|
||||
@override
|
||||
void patternAssignment_afterRhs(Expression rhs) {}
|
||||
|
||||
@override
|
||||
void patternAssignment_end() {}
|
||||
|
||||
@override
|
||||
void patternVariableDeclaration_afterInitializer(Expression initializer) {}
|
||||
|
||||
|
@ -4858,9 +4903,6 @@ class _LegacyTypePromotion<Node extends Object, Statement extends Node,
|
|||
@override
|
||||
void tryFinallyStatement_finallyBegin(Node body) {}
|
||||
|
||||
@override
|
||||
void variablePattern({required Type matchedType, required Type staticType}) {}
|
||||
|
||||
@override
|
||||
Type? variableRead(Expression expression, Variable variable) {
|
||||
int variableKey = _promotionKeyStore.keyForVariable(variable);
|
||||
|
|
|
@ -243,15 +243,51 @@ mixin TypeAnalyzer<
|
|||
/// Returns the type `Object?`.
|
||||
Type get objectQuestionType;
|
||||
|
||||
/// The [Operations], used to access types, check subtyping, and query
|
||||
/// variable types.
|
||||
Operations<Variable, Type> get operations;
|
||||
|
||||
/// Options affecting the behavior of [TypeAnalyzer].
|
||||
TypeAnalyzerOptions get options;
|
||||
|
||||
/// Returns the client's implementation of the [TypeOperations] class.
|
||||
TypeOperations<Type> get typeOperations;
|
||||
|
||||
/// Returns the unknown type context (`?`) used in type inference.
|
||||
Type get unknownType;
|
||||
|
||||
/// Analyzes a non-wildcard variable pattern appearing in an assignment
|
||||
/// context. [node] is the pattern itself, and [variable] is the variable
|
||||
/// being referenced.
|
||||
///
|
||||
/// See [dispatchPattern] for the meanings of [matchedType] and [context].
|
||||
///
|
||||
/// For wildcard patterns in an assignment context,
|
||||
/// [analyzeDeclaredVariablePattern] should be used instead.
|
||||
///
|
||||
/// Stack effect: none.
|
||||
void analyzeAssignedVariablePattern(
|
||||
Type matchedType,
|
||||
MatchContext<Node, Expression, Pattern, Type, Variable> context,
|
||||
Pattern node,
|
||||
Variable variable) {
|
||||
Type variableDeclaredType = operations.variableType(variable);
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
assert(irrefutableContext != null,
|
||||
'Assigned variables must only appear in irrefutable pattern contexts');
|
||||
if (irrefutableContext != null &&
|
||||
!operations.isAssignableTo(matchedType, variableDeclaredType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
matchedType: matchedType,
|
||||
requiredType: variableDeclaredType);
|
||||
}
|
||||
flow?.write(node, variable, matchedType, context.getInitializer(node));
|
||||
}
|
||||
|
||||
/// Computes the type schema for a variable pattern appearing in an assignment
|
||||
/// context. [variable] is the variable being referenced.
|
||||
Type analyzeAssignedVariablePatternSchema(Variable variable) =>
|
||||
flow?.promotedType(variable) ?? operations.variableType(variable);
|
||||
|
||||
/// Analyzes a cast pattern. [innerPattern] is the sub-pattern] and [type] is
|
||||
/// the type to cast to.
|
||||
///
|
||||
|
@ -299,8 +335,8 @@ mixin TypeAnalyzer<
|
|||
if (switchScrutinee != null) {
|
||||
bool nullSafetyEnabled = options.nullSafetyEnabled;
|
||||
bool matches = nullSafetyEnabled
|
||||
? typeOperations.isSubtypeOf(staticType, matchedType)
|
||||
: typeOperations.isAssignableTo(staticType, matchedType);
|
||||
? operations.isSubtypeOf(staticType, matchedType)
|
||||
: operations.isAssignableTo(staticType, matchedType);
|
||||
if (!matches) {
|
||||
errors.caseExpressionTypeMismatch(
|
||||
caseExpression: expression,
|
||||
|
@ -324,19 +360,79 @@ mixin TypeAnalyzer<
|
|||
return unknownType;
|
||||
}
|
||||
|
||||
/// Analyzes a variable pattern in a non-assignment context (or a wildcard
|
||||
/// pattern). [node] is the pattern itself, [variable] is the variable,
|
||||
/// [declaredType] is the explicitly declared type (if present), and [isFinal]
|
||||
/// indicates whether the variable is final.
|
||||
///
|
||||
/// See [dispatchPattern] for the meanings of [matchedType] and [context].
|
||||
///
|
||||
/// If this is a wildcard pattern (it doesn't bind any variable), [variable]
|
||||
/// should be `null`.
|
||||
///
|
||||
/// Returns the static type of the variable (possibly inferred).
|
||||
///
|
||||
/// Stack effect: none.
|
||||
Type analyzeDeclaredVariablePattern(
|
||||
Type matchedType,
|
||||
MatchContext<Node, Expression, Pattern, Type, Variable> context,
|
||||
Pattern node,
|
||||
Variable? variable,
|
||||
String? name,
|
||||
Type? declaredType,
|
||||
) {
|
||||
Type staticType =
|
||||
declaredType ?? variableTypeFromInitializerType(matchedType);
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null &&
|
||||
!operations.isAssignableTo(matchedType, staticType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
matchedType: matchedType,
|
||||
requiredType: staticType);
|
||||
}
|
||||
flow?.declaredVariablePattern(
|
||||
matchedType: matchedType, staticType: staticType);
|
||||
bool isImplicitlyTyped = declaredType == null;
|
||||
if (variable != null) {
|
||||
if (name == null) {
|
||||
throw new StateError(
|
||||
'When the variable is not null, the name must also be not null');
|
||||
}
|
||||
flow?.declare(variable, false);
|
||||
setVariableType(variable, staticType);
|
||||
// TODO(paulberry): are we handling _isFinal correctly?
|
||||
flow?.initialize(variable, matchedType, context.getInitializer(node),
|
||||
isFinal: context.isFinal || isVariableFinal(variable),
|
||||
isLate: context.isLate,
|
||||
isImplicitlyTyped: isImplicitlyTyped);
|
||||
}
|
||||
return staticType;
|
||||
}
|
||||
|
||||
/// Computes the type schema for a variable pattern in a non-assignment
|
||||
/// context (or a wildcard pattern). [declaredType] is the explicitly
|
||||
/// declared type (if present).
|
||||
///
|
||||
/// Stack effect: none.
|
||||
Type analyzeDeclaredVariablePatternSchema(Type? declaredType) {
|
||||
return declaredType ?? unknownType;
|
||||
}
|
||||
|
||||
/// Analyzes an expression. [node] is the expression to analyze, and
|
||||
/// [context] is the type schema which should be used for type inference.
|
||||
///
|
||||
/// Stack effect: pushes (Expression).
|
||||
Type analyzeExpression(Expression node, Type? context) {
|
||||
// Stack: ()
|
||||
if (context == null || typeOperations.isDynamic(context)) {
|
||||
if (context == null || operations.isDynamic(context)) {
|
||||
context = unknownType;
|
||||
}
|
||||
ExpressionTypeAnalysisResult<Type> result =
|
||||
dispatchExpression(node, context);
|
||||
// Stack: (Expression)
|
||||
if (typeOperations.isNever(result.provisionalType)) {
|
||||
if (operations.isNever(result.provisionalType)) {
|
||||
flow?.handleExit();
|
||||
}
|
||||
return result.resolveShorting();
|
||||
|
@ -501,8 +597,8 @@ mixin TypeAnalyzer<
|
|||
///
|
||||
/// Stack effect: none.
|
||||
IntTypeAnalysisResult<Type> analyzeIntLiteral(Type context) {
|
||||
bool convertToDouble = !typeOperations.isSubtypeOf(intType, context) &&
|
||||
typeOperations.isSubtypeOf(doubleType, context);
|
||||
bool convertToDouble = !operations.isSubtypeOf(intType, context) &&
|
||||
operations.isSubtypeOf(doubleType, context);
|
||||
Type type = convertToDouble ? doubleType : intType;
|
||||
return new IntTypeAnalysisResult<Type>(
|
||||
type: type, convertedToDouble: convertToDouble);
|
||||
|
@ -525,10 +621,10 @@ mixin TypeAnalyzer<
|
|||
if (elementType != null) {
|
||||
valueType = elementType;
|
||||
} else {
|
||||
Type? listElementType = typeOperations.matchListType(matchedType);
|
||||
Type? listElementType = operations.matchListType(matchedType);
|
||||
if (listElementType != null) {
|
||||
valueType = listElementType;
|
||||
} else if (typeOperations.isDynamic(matchedType)) {
|
||||
} else if (operations.isDynamic(matchedType)) {
|
||||
valueType = dynamicType;
|
||||
} else {
|
||||
valueType = objectQuestionType;
|
||||
|
@ -550,7 +646,7 @@ mixin TypeAnalyzer<
|
|||
Type requiredType = listType(valueType);
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null &&
|
||||
!typeOperations.isAssignableTo(matchedType, requiredType)) {
|
||||
!operations.isAssignableTo(matchedType, requiredType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
|
@ -584,7 +680,7 @@ mixin TypeAnalyzer<
|
|||
Pattern? subPattern = getRestPatternElementPattern(element);
|
||||
if (subPattern != null) {
|
||||
Type subPatternType = dispatchPatternSchema(subPattern);
|
||||
typeToAdd = typeOperations.matchIterableType(subPatternType);
|
||||
typeToAdd = operations.matchIterableType(subPatternType);
|
||||
}
|
||||
} else {
|
||||
typeToAdd = dispatchPatternSchema(element);
|
||||
|
@ -593,7 +689,7 @@ mixin TypeAnalyzer<
|
|||
if (currentGLB == null) {
|
||||
currentGLB = typeToAdd;
|
||||
} else {
|
||||
currentGLB = typeOperations.glb(currentGLB, typeToAdd);
|
||||
currentGLB = operations.glb(currentGLB, typeToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -644,7 +740,7 @@ mixin TypeAnalyzer<
|
|||
/// Stack effect: none.
|
||||
Type analyzeLogicalPatternSchema(Node lhs, Node rhs, {required bool isAnd}) {
|
||||
if (isAnd) {
|
||||
return typeOperations.glb(
|
||||
return operations.glb(
|
||||
dispatchPatternSchema(lhs), dispatchPatternSchema(rhs));
|
||||
} else {
|
||||
// Logical-or patterns are only allowed in refutable contexts, and
|
||||
|
@ -677,12 +773,12 @@ mixin TypeAnalyzer<
|
|||
valueType = typeArguments.valueType;
|
||||
keyContext = keyType;
|
||||
} else {
|
||||
typeArguments = typeOperations.matchMapType(matchedType);
|
||||
typeArguments = operations.matchMapType(matchedType);
|
||||
if (typeArguments != null) {
|
||||
keyType = typeArguments.keyType;
|
||||
valueType = typeArguments.valueType;
|
||||
keyContext = keyType;
|
||||
} else if (typeOperations.isDynamic(matchedType)) {
|
||||
} else if (operations.isDynamic(matchedType)) {
|
||||
keyType = dynamicType;
|
||||
valueType = dynamicType;
|
||||
keyContext = unknownType;
|
||||
|
@ -716,7 +812,7 @@ mixin TypeAnalyzer<
|
|||
);
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null &&
|
||||
!typeOperations.isAssignableTo(matchedType, requiredType)) {
|
||||
!operations.isAssignableTo(matchedType, requiredType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
|
@ -751,7 +847,7 @@ mixin TypeAnalyzer<
|
|||
if (valueType == null) {
|
||||
valueType = entryValueType;
|
||||
} else {
|
||||
valueType = typeOperations.glb(valueType, entryValueType);
|
||||
valueType = operations.glb(valueType, entryValueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -775,7 +871,7 @@ mixin TypeAnalyzer<
|
|||
Pattern innerPattern,
|
||||
{required bool isAssert}) {
|
||||
// Stack: ()
|
||||
Type innerMatchedType = typeOperations.promoteToNonNull(matchedType);
|
||||
Type innerMatchedType = operations.promoteToNonNull(matchedType);
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null && !isAssert) {
|
||||
errors?.refutablePatternInIrrefutableContext(node, irrefutableContext);
|
||||
|
@ -794,7 +890,7 @@ mixin TypeAnalyzer<
|
|||
Type analyzeNullCheckOrAssertPatternSchema(Pattern innerPattern,
|
||||
{required bool isAssert}) {
|
||||
if (isAssert) {
|
||||
return typeOperations.makeNullable(dispatchPatternSchema(innerPattern));
|
||||
return operations.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.
|
||||
|
@ -828,14 +924,14 @@ mixin TypeAnalyzer<
|
|||
// If the required type is `dynamic` or `Never`, then every getter is
|
||||
// treated as having the same type.
|
||||
Type? overridePropertyGetType;
|
||||
if (typeOperations.isDynamic(requiredType) ||
|
||||
typeOperations.isNever(requiredType)) {
|
||||
if (operations.isDynamic(requiredType) ||
|
||||
operations.isNever(requiredType)) {
|
||||
overridePropertyGetType = requiredType;
|
||||
}
|
||||
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null &&
|
||||
!typeOperations.isAssignableTo(matchedType, requiredType)) {
|
||||
!operations.isAssignableTo(matchedType, requiredType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
|
@ -866,6 +962,33 @@ mixin TypeAnalyzer<
|
|||
return type;
|
||||
}
|
||||
|
||||
/// Analyzes a patternAssignment expression of the form `pattern = rhs`.
|
||||
///
|
||||
/// [node] should be the AST node for the entire expression, [pattern] for
|
||||
/// the pattern, and [rhs] for the right hand side.
|
||||
///
|
||||
/// Stack effect: pushes (Expression, Pattern).
|
||||
ExpressionTypeAnalysisResult<Type> analyzePatternAssignment(
|
||||
Expression node, Pattern pattern, Expression rhs) {
|
||||
// Stack: ()
|
||||
Type rhsType = analyzeExpression(rhs, dispatchPatternSchema(pattern));
|
||||
// Stack: (Expression)
|
||||
flow?.patternAssignment_afterRhs(rhs);
|
||||
dispatchPattern(
|
||||
rhsType,
|
||||
new MatchContext<Node, Expression, Pattern, Type, Variable>(
|
||||
isFinal: false,
|
||||
initializer: rhs,
|
||||
irrefutableContext: node,
|
||||
topPattern: pattern,
|
||||
),
|
||||
pattern,
|
||||
);
|
||||
flow?.patternAssignment_end();
|
||||
// Stack: (Expression, Pattern)
|
||||
return new SimpleTypeAnalysisResult<Type>(type: rhsType);
|
||||
}
|
||||
|
||||
/// Analyzes a patternVariableDeclaration statement of the form
|
||||
/// `var pattern = initializer;` or `final pattern = initializer;.
|
||||
///
|
||||
|
@ -971,7 +1094,7 @@ mixin TypeAnalyzer<
|
|||
} else {
|
||||
dispatchFields(objectQuestionType);
|
||||
}
|
||||
} else if (typeOperations.isDynamic(matchedType)) {
|
||||
} else if (operations.isDynamic(matchedType)) {
|
||||
dispatchFields(dynamicType);
|
||||
} else {
|
||||
dispatchFields(objectQuestionType);
|
||||
|
@ -980,7 +1103,7 @@ mixin TypeAnalyzer<
|
|||
|
||||
Node? irrefutableContext = context.irrefutableContext;
|
||||
if (irrefutableContext != null &&
|
||||
!typeOperations.isAssignableTo(matchedType, requiredType)) {
|
||||
!operations.isAssignableTo(matchedType, requiredType)) {
|
||||
errors?.patternTypeMismatchInIrrefutableContext(
|
||||
pattern: node,
|
||||
context: irrefutableContext,
|
||||
|
@ -1036,17 +1159,16 @@ mixin TypeAnalyzer<
|
|||
// Stack: (Expression)
|
||||
if (errors != null && operator != null) {
|
||||
Type argumentType = operator.isEquality
|
||||
? typeOperations.promoteToNonNull(operandType)
|
||||
? operations.promoteToNonNull(operandType)
|
||||
: operandType;
|
||||
if (!typeOperations.isAssignableTo(
|
||||
argumentType, operator.parameterType)) {
|
||||
if (!operations.isAssignableTo(argumentType, operator.parameterType)) {
|
||||
errors.argumentTypeNotAssignable(
|
||||
argument: operand,
|
||||
argumentType: argumentType,
|
||||
parameterType: operator.parameterType,
|
||||
);
|
||||
}
|
||||
if (!typeOperations.isAssignableTo(operator.returnType, boolType)) {
|
||||
if (!operations.isAssignableTo(operator.returnType, boolType)) {
|
||||
errors.relationalPatternOperatorReturnTypeNotAssignableToBool(
|
||||
node: node,
|
||||
returnType: operator.returnType,
|
||||
|
@ -1118,7 +1240,7 @@ mixin TypeAnalyzer<
|
|||
if (lubType == null) {
|
||||
lubType = type;
|
||||
} else {
|
||||
lubType = typeOperations.lub(lubType, type);
|
||||
lubType = operations.lub(lubType, type);
|
||||
}
|
||||
finishExpressionCase(node, i);
|
||||
// Stack: (Expression, (i + 1) * ExpressionCase)
|
||||
|
@ -1250,62 +1372,6 @@ mixin TypeAnalyzer<
|
|||
return inferredType;
|
||||
}
|
||||
|
||||
/// Analyzes a variable pattern. [node] is the pattern itself, [variable] is
|
||||
/// the variable, [declaredType] is the explicitly declared type (if present),
|
||||
/// and [isFinal] indicates whether the variable is final.
|
||||
///
|
||||
/// See [dispatchPattern] for the meanings of [matchedType] and [context].
|
||||
///
|
||||
/// If this is a wildcard pattern (it doesn't bind any variable), [variable]
|
||||
/// should be `null`.
|
||||
///
|
||||
/// Returns the static type of the variable (possibly inferred).
|
||||
///
|
||||
/// Stack effect: none.
|
||||
Type analyzeVariablePattern(
|
||||
Type matchedType,
|
||||
MatchContext<Node, Expression, Pattern, Type, Variable> context,
|
||||
Pattern node,
|
||||
Variable? variable,
|
||||
String? name,
|
||||
Type? declaredType,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
flow?.variablePattern(matchedType: matchedType, staticType: staticType);
|
||||
bool isImplicitlyTyped = declaredType == null;
|
||||
if (variable != null) {
|
||||
if (name == null) {
|
||||
throw new StateError(
|
||||
'When the variable is not null, the name must also be not null');
|
||||
}
|
||||
flow?.declare(variable, false);
|
||||
setVariableType(variable, staticType);
|
||||
// TODO(paulberry): are we handling _isFinal correctly?
|
||||
flow?.initialize(variable, matchedType, context.getInitializer(node),
|
||||
isFinal: context.isFinal || isVariableFinal(variable),
|
||||
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;
|
||||
|
||||
/// If [type] is a record type, returns it.
|
||||
RecordType<Type>? asRecordType(Type type);
|
||||
|
||||
|
@ -1633,7 +1699,7 @@ mixin TypeAnalyzer<
|
|||
// TODO(paulberry): spec says the type must be `bool` or `dynamic`. This
|
||||
// logic permits `T extends bool`, `T promoted to bool`, or `Never`. What
|
||||
// do we want?
|
||||
if (!typeOperations.isAssignableTo(type, boolType)) {
|
||||
if (!operations.isAssignableTo(type, boolType)) {
|
||||
errors?.nonBooleanCondition(expression);
|
||||
}
|
||||
}
|
||||
|
@ -1756,9 +1822,9 @@ mixin TypeAnalyzer<
|
|||
}
|
||||
|
||||
bool _structurallyEqualAfterNormTypes(Type type1, Type type2) {
|
||||
Type norm1 = typeOperations.normalize(type1);
|
||||
Type norm2 = typeOperations.normalize(type2);
|
||||
return typeOperations.areStructurallyEqual(norm1, norm2);
|
||||
Type norm1 = operations.normalize(type1);
|
||||
Type norm2 = operations.normalize(type2);
|
||||
return operations.areStructurallyEqual(norm1, norm2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class FlowAnalysisTestHarness extends Harness implements FlowModelHelper<Type> {
|
|||
final PromotionKeyStore<Var> promotionKeyStore = PromotionKeyStore();
|
||||
|
||||
@override
|
||||
Operations<Var, Type> get typeOperations => typeAnalyzer.typeOperations;
|
||||
Operations<Var, Type> get typeOperations => typeAnalyzer.operations;
|
||||
}
|
||||
|
||||
/// Helper class allowing tests to examine the values of variables' SSA nodes.
|
||||
|
|
|
@ -5838,6 +5838,26 @@ main() {
|
|||
]);
|
||||
});
|
||||
|
||||
test('due to pattern assignment', () {
|
||||
var x = Var('x');
|
||||
late Pattern writePattern;
|
||||
h.run([
|
||||
declare(x, type: 'int?', initializer: expr('int?')),
|
||||
if_(x.expr.eq(nullLiteral), [
|
||||
return_(),
|
||||
]),
|
||||
checkPromoted(x, 'int'),
|
||||
(writePattern = x.pattern()).assign(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
x.expr.whyNotPromoted((reasons) {
|
||||
expect(reasons.keys, unorderedEquals([Type('int')]));
|
||||
var nonPromotionReason =
|
||||
reasons.values.single as DemoteViaExplicitWrite<Var>;
|
||||
expect(nonPromotionReason.node, same(writePattern));
|
||||
}).stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('preserved in join when one branch unreachable', () {
|
||||
var x = Var('x');
|
||||
late Expression writeExpression;
|
||||
|
@ -6377,6 +6397,118 @@ main() {
|
|||
});
|
||||
|
||||
group('Patterns:', () {
|
||||
group('Assignment:', () {
|
||||
group('Demotion', () {
|
||||
test('Demoting', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
x.expr.nonNullAssert.stmt,
|
||||
checkPromoted(x, 'int'),
|
||||
x.pattern().assign(expr('int?')).stmt,
|
||||
checkNotPromoted(x),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Non-demoting', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num?'),
|
||||
x.expr.nonNullAssert.stmt,
|
||||
checkPromoted(x, 'num'),
|
||||
x.pattern().assign(expr('int')).stmt,
|
||||
checkPromoted(x, 'num'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Schema:', () {
|
||||
test('Not promoted', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
x.pattern().assign(expr('int').checkContext('int?')).stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('Promoted', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
x.expr.nonNullAssert.stmt,
|
||||
checkPromoted(x, 'int'),
|
||||
x.pattern().assign(expr('int').checkContext('int')).stmt,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('Promotion:', () {
|
||||
test('Type of interest', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
if_(x.expr.is_('int'), []),
|
||||
checkNotPromoted(x),
|
||||
x.pattern().assign(expr('int')).stmt,
|
||||
checkPromoted(x, 'int'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Not a type of interest', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
x.pattern().assign(expr('int')).stmt,
|
||||
checkNotPromoted(x),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Definite assignment', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int'),
|
||||
checkAssigned(x, false),
|
||||
x.pattern().assign(expr('int')).stmt,
|
||||
checkAssigned(x, true),
|
||||
]);
|
||||
});
|
||||
|
||||
group('Boolean condition', () {
|
||||
test('As main pattern', () {
|
||||
var b = Var('b');
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
declare(b, type: 'bool'),
|
||||
b.pattern().assign(x.expr.notEq(nullLiteral)).stmt,
|
||||
if_(b.expr, [
|
||||
// `x` is promoted because `b` is known to equal `x != null`.
|
||||
checkPromoted(x, 'int'),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
test('As subpattern', () {
|
||||
var b = Var('b');
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'int?'),
|
||||
declare(b, type: 'bool'),
|
||||
recordPattern([b.pattern().recordField()])
|
||||
.assign(x.expr.notEq(nullLiteral).as_('dynamic'))
|
||||
.stmt,
|
||||
if_(b.expr, [
|
||||
// Even though the RHS of the pattern is `x != null`, `x` is not
|
||||
// promoted because the pattern for `b` is in a subpattern
|
||||
// position.
|
||||
checkNotPromoted(x),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('If-case element:', () {
|
||||
test('guarded', () {
|
||||
var x = Var('x');
|
||||
|
|
|
@ -539,7 +539,8 @@ class ExpressionCase extends Node {
|
|||
void _preVisit(PreVisitor visitor) {
|
||||
var variableBinder = _VariableBinder(errors: visitor.errors);
|
||||
variableBinder.casePatternStart();
|
||||
guardedPattern?.pattern.preVisit(visitor, variableBinder);
|
||||
guardedPattern?.pattern
|
||||
.preVisit(visitor, variableBinder, isInAssignment: false);
|
||||
variableBinder.casePatternFinish();
|
||||
variableBinder.finish();
|
||||
expression.preVisit(visitor);
|
||||
|
@ -1251,6 +1252,10 @@ abstract class Pattern extends Node
|
|||
Pattern as_(String type) =>
|
||||
new _CastPattern(this, Type(type), location: computeLocation());
|
||||
|
||||
/// Creates a pattern assignment expression assigning [rhs] to this pattern.
|
||||
Expression assign(Expression rhs) =>
|
||||
_PatternAssignment(this, rhs, location: computeLocation());
|
||||
|
||||
Type computeSchema(Harness h);
|
||||
|
||||
Pattern or(Pattern other) =>
|
||||
|
@ -1633,8 +1638,9 @@ class _CastPattern extends Pattern {
|
|||
Type computeSchema(Harness h) => h.typeAnalyzer.analyzeCastPatternSchema();
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
_inner.preVisit(visitor, variableBinder);
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
_inner.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1980,7 +1986,8 @@ class _ConstantPattern extends Pattern {
|
|||
h.typeAnalyzer.analyzeConstantPatternSchema();
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
constant.preVisit(visitor);
|
||||
}
|
||||
|
||||
|
@ -2029,7 +2036,7 @@ class _Declare extends Statement {
|
|||
void preVisit(PreVisitor visitor) {
|
||||
var variableBinder = _VariableBinder(errors: visitor.errors);
|
||||
variableBinder.casePatternStart();
|
||||
pattern.preVisit(visitor, variableBinder);
|
||||
pattern.preVisit(visitor, variableBinder, isInAssignment: false);
|
||||
variableBinder.casePatternFinish();
|
||||
variableBinder.finish();
|
||||
if (isLate) {
|
||||
|
@ -2062,7 +2069,7 @@ class _Declare extends Statement {
|
|||
var staticType = h.typeAnalyzer.analyzeUninitializedVariableDeclaration(
|
||||
this, pattern.variable!, pattern.declaredType,
|
||||
isFinal: isFinal, isLate: isLate);
|
||||
h.typeAnalyzer.handleVariablePattern(pattern,
|
||||
h.typeAnalyzer.handleDeclaredVariablePattern(pattern,
|
||||
matchedType: staticType, staticType: staticType);
|
||||
irName = 'declare';
|
||||
argKinds = [Kind.pattern];
|
||||
|
@ -2394,7 +2401,7 @@ class _IfCase extends _IfBase {
|
|||
_expression.preVisit(visitor);
|
||||
var variableBinder = _VariableBinder(errors: visitor.errors);
|
||||
variableBinder.casePatternStart();
|
||||
_pattern.preVisit(visitor, variableBinder);
|
||||
_pattern.preVisit(visitor, variableBinder, isInAssignment: false);
|
||||
_candidateVariables = variableBinder.casePatternFinish();
|
||||
variableBinder.finish();
|
||||
_guard?.preVisit(visitor);
|
||||
|
@ -2438,7 +2445,7 @@ class _IfCaseElement extends _IfElementBase {
|
|||
_expression.preVisit(visitor);
|
||||
var variableBinder = _VariableBinder(errors: visitor.errors);
|
||||
variableBinder.casePatternStart();
|
||||
_pattern.preVisit(visitor, variableBinder);
|
||||
_pattern.preVisit(visitor, variableBinder, isInAssignment: false);
|
||||
variableBinder.casePatternFinish();
|
||||
variableBinder.finish();
|
||||
_guard?.preVisit(visitor);
|
||||
|
@ -2626,7 +2633,8 @@ class _LabeledStatement extends Statement {
|
|||
}
|
||||
|
||||
abstract class _ListOrMapPatternElement implements Node {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder);
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment});
|
||||
|
||||
String _debugString({required bool needsKeywordOrType});
|
||||
}
|
||||
|
@ -2644,9 +2652,10 @@ class _ListPattern extends Pattern {
|
|||
.analyzeListPatternSchema(elementType: _elementType, elements: _elements);
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
for (var element in _elements) {
|
||||
element.preVisit(visitor, variableBinder);
|
||||
element.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2749,15 +2758,16 @@ class _LogicalPattern extends Pattern {
|
|||
h.typeAnalyzer.analyzeLogicalPatternSchema(_lhs, _rhs, isAnd: isAnd);
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
if (isAnd) {
|
||||
_lhs.preVisit(visitor, variableBinder);
|
||||
_rhs.preVisit(visitor, variableBinder);
|
||||
_lhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
_rhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
} else {
|
||||
variableBinder.logicalOrPatternStart();
|
||||
_lhs.preVisit(visitor, variableBinder);
|
||||
_lhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
variableBinder.logicalOrPatternFinishLeft();
|
||||
_rhs.preVisit(visitor, variableBinder);
|
||||
_rhs.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
variableBinder.logicalOrPatternFinish(this);
|
||||
}
|
||||
}
|
||||
|
@ -2813,9 +2823,10 @@ class _MapPattern extends Pattern {
|
|||
typeArguments: _typeArguments, elements: _elements);
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
for (var element in _elements) {
|
||||
element.preVisit(visitor, variableBinder);
|
||||
element.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2856,8 +2867,9 @@ class _MapPatternEntry extends Node implements MapPatternElement {
|
|||
_MapPatternEntry(this.key, this.value, {required super.location}) : super._();
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
value.preVisit(visitor, variableBinder);
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
value.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3065,10 +3077,10 @@ class _MiniAstTypeAnalyzer
|
|||
FlowAnalysis<Node, Statement, Expression, Var, Type> get flow =>
|
||||
_harness.flow;
|
||||
|
||||
Type get thisType => _harness._thisType!;
|
||||
|
||||
@override
|
||||
MiniAstOperations get typeOperations => _harness._operations;
|
||||
MiniAstOperations get operations => _harness._operations;
|
||||
|
||||
Type get thisType => _harness._thisType!;
|
||||
|
||||
void analyzeAssertStatement(
|
||||
Statement node, Expression condition, Expression? message) {
|
||||
|
@ -3372,7 +3384,7 @@ class _MiniAstTypeAnalyzer
|
|||
if (requiredType.args.isNotEmpty) {
|
||||
return requiredType;
|
||||
} else {
|
||||
return typeOperations.downwardInfer(requiredType.name, matchedType);
|
||||
return operations.downwardInfer(requiredType.name, matchedType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3491,6 +3503,15 @@ class _MiniAstTypeAnalyzer
|
|||
);
|
||||
}
|
||||
|
||||
void handleAssignedVariablePattern(covariant _VariablePattern node) {
|
||||
_irBuilder.atom(node.variable!.name, Kind.variable,
|
||||
location: node.location);
|
||||
_irBuilder.apply('assignedVarPattern', [Kind.variable], Kind.pattern,
|
||||
location: node.location);
|
||||
assert(node.expectInferredType == null,
|
||||
"assigned variable patterns don't get an inferred type");
|
||||
}
|
||||
|
||||
@override
|
||||
void handleCase_afterCaseHeads(
|
||||
covariant _SwitchStatement node, int caseIndex, Iterable<Var> variables) {
|
||||
|
@ -3524,6 +3545,21 @@ class _MiniAstTypeAnalyzer
|
|||
location: node.location);
|
||||
}
|
||||
|
||||
void handleDeclaredVariablePattern(covariant _VariablePattern node,
|
||||
{required Type matchedType, required Type staticType}) {
|
||||
_irBuilder.atom(node.variable?.name ?? '_', Kind.variable,
|
||||
location: node.location);
|
||||
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
|
||||
_irBuilder.atom(staticType.type, Kind.type, location: node.location);
|
||||
_irBuilder.apply(
|
||||
'varPattern', [Kind.variable, Kind.type, Kind.type], Kind.pattern,
|
||||
names: ['matchedType', 'staticType'], location: node.location);
|
||||
var expectInferredType = node.expectInferredType;
|
||||
if (expectInferredType != null) {
|
||||
expect(staticType.type, expectInferredType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleDefault(Node node, int caseIndex) {
|
||||
_irBuilder.atom('default', Kind.caseHead, location: node.location);
|
||||
|
@ -3610,21 +3646,6 @@ class _MiniAstTypeAnalyzer
|
|||
@override
|
||||
void handleSwitchScrutinee(Type type) {}
|
||||
|
||||
void handleVariablePattern(covariant _VariablePattern node,
|
||||
{required Type matchedType, required Type staticType}) {
|
||||
_irBuilder.atom(node.variable?.name ?? '_', Kind.variable,
|
||||
location: node.location);
|
||||
_irBuilder.atom(matchedType.type, Kind.type, location: node.location);
|
||||
_irBuilder.atom(staticType.type, Kind.type, location: node.location);
|
||||
_irBuilder.apply(
|
||||
'varPattern', [Kind.variable, Kind.type, Kind.type], Kind.pattern,
|
||||
names: ['matchedType', 'staticType'], location: node.location);
|
||||
var expectInferredType = node.expectInferredType;
|
||||
if (expectInferredType != null) {
|
||||
expect(staticType.type, expectInferredType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isRestPatternElement(Node element) {
|
||||
return element is _RestPatternElement;
|
||||
|
@ -3807,8 +3828,9 @@ class _NullCheckOrAssertPattern extends Pattern {
|
|||
.analyzeNullCheckOrAssertPatternSchema(_inner, isAssert: _isAssert);
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
_inner.preVisit(visitor, variableBinder);
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
_inner.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3864,12 +3886,11 @@ class _ObjectPattern extends Pattern {
|
|||
}
|
||||
|
||||
@override
|
||||
void preVisit(
|
||||
PreVisitor visitor,
|
||||
VariableBinder<Node, Var> variableBinder,
|
||||
) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
for (var field in fields) {
|
||||
field.pattern.preVisit(visitor, variableBinder);
|
||||
field.pattern
|
||||
.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3922,6 +3943,32 @@ class _ParenthesizedExpression extends Expression {
|
|||
}
|
||||
}
|
||||
|
||||
class _PatternAssignment extends Expression {
|
||||
final Pattern _pattern;
|
||||
final Expression _rhs;
|
||||
|
||||
_PatternAssignment(this._pattern, this._rhs, {required super.location});
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor) {
|
||||
var variableBinder = _VariableBinder(errors: visitor.errors);
|
||||
variableBinder.casePatternStart();
|
||||
_pattern.preVisit(visitor, variableBinder, isInAssignment: true);
|
||||
variableBinder.casePatternFinish();
|
||||
variableBinder.finish();
|
||||
_rhs.preVisit(visitor);
|
||||
}
|
||||
|
||||
@override
|
||||
ExpressionTypeAnalysisResult<Type> visit(Harness h, Type context) {
|
||||
var result = h.typeAnalyzer.analyzePatternAssignment(this, _pattern, _rhs);
|
||||
h.irBuilder.apply(
|
||||
'patternAssignment', [Kind.expression, Kind.pattern], Kind.expression,
|
||||
location: location);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class _PlaceholderExpression extends Expression {
|
||||
final Type type;
|
||||
|
||||
|
@ -3998,12 +4045,11 @@ class _RecordPattern extends Pattern {
|
|||
}
|
||||
|
||||
@override
|
||||
void preVisit(
|
||||
PreVisitor visitor,
|
||||
VariableBinder<Node, Var> variableBinder,
|
||||
) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
for (var field in fields) {
|
||||
field.pattern.preVisit(visitor, variableBinder);
|
||||
field.pattern
|
||||
.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4048,7 +4094,8 @@ class _RelationalPattern extends Pattern {
|
|||
h.typeAnalyzer.analyzeRelationalPatternSchema();
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
operand.preVisit(visitor);
|
||||
}
|
||||
|
||||
|
@ -4077,8 +4124,9 @@ class _RestPatternElement extends Node
|
|||
_RestPatternElement(this._pattern, {required super.location}) : super._();
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
_pattern?.preVisit(visitor, variableBinder);
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
_pattern?.preVisit(visitor, variableBinder, isInAssignment: isInAssignment);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4255,7 +4303,8 @@ class _SwitchStatementMember extends Node {
|
|||
for (SwitchHead element in elements) {
|
||||
if (element is _SwitchHeadCase) {
|
||||
variableBinder.casePatternStart();
|
||||
element.guardedPattern.pattern.preVisit(visitor, variableBinder);
|
||||
element.guardedPattern.pattern
|
||||
.preVisit(visitor, variableBinder, isInAssignment: false);
|
||||
element.guardedPattern.guard?.preVisit(visitor);
|
||||
element.guardedPattern.variables = variableBinder.casePatternFinish(
|
||||
sharedCaseScopeKey: this,
|
||||
|
@ -4441,20 +4490,35 @@ class _VariablePattern extends Pattern {
|
|||
|
||||
final String? expectInferredType;
|
||||
|
||||
late bool isAssignedVariable;
|
||||
|
||||
_VariablePattern(this.declaredType, this.variable, this.expectInferredType,
|
||||
{required super.location})
|
||||
: super._();
|
||||
|
||||
@override
|
||||
Type computeSchema(Harness h) =>
|
||||
h.typeAnalyzer.analyzeVariablePatternSchema(declaredType);
|
||||
Type computeSchema(Harness h) {
|
||||
if (isAssignedVariable) {
|
||||
return h.typeAnalyzer.analyzeAssignedVariablePatternSchema(variable!);
|
||||
} else {
|
||||
return h.typeAnalyzer.analyzeDeclaredVariablePatternSchema(declaredType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder) {
|
||||
void preVisit(PreVisitor visitor, VariableBinder<Node, Var> variableBinder,
|
||||
{required bool isInAssignment}) {
|
||||
var variable = this.variable;
|
||||
if (variable != null && variableBinder.add(variable.name, variable)) {
|
||||
isAssignedVariable = isInAssignment && variable != null;
|
||||
if (!isAssignedVariable &&
|
||||
variable != null &&
|
||||
variableBinder.add(variable.name, variable)) {
|
||||
visitor._assignedVariables.declare(variable);
|
||||
}
|
||||
if (isAssignedVariable) {
|
||||
assert(declaredType == null,
|
||||
"Variables in pattern assignments can't have declared types");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4463,10 +4527,16 @@ class _VariablePattern extends Pattern {
|
|||
Type matchedType,
|
||||
SharedMatchContext context,
|
||||
) {
|
||||
var staticType = h.typeAnalyzer.analyzeVariablePattern(
|
||||
matchedType, context, this, variable, variable?.name, declaredType);
|
||||
h.typeAnalyzer.handleVariablePattern(this,
|
||||
matchedType: matchedType, staticType: staticType);
|
||||
if (isAssignedVariable) {
|
||||
h.typeAnalyzer.analyzeAssignedVariablePattern(
|
||||
matchedType, context, this, variable!);
|
||||
h.typeAnalyzer.handleAssignedVariablePattern(this);
|
||||
} else {
|
||||
var staticType = h.typeAnalyzer.analyzeDeclaredVariablePattern(
|
||||
matchedType, context, this, variable, variable?.name, declaredType);
|
||||
h.typeAnalyzer.handleDeclaredVariablePattern(this,
|
||||
matchedType: matchedType, staticType: staticType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -2418,6 +2418,67 @@ main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('Pattern assignment:', () {
|
||||
test('Static type', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
x.pattern().assign(expr('int')).checkType('int').stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('RHS context', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
x
|
||||
.pattern()
|
||||
.assign(expr('int').checkContext('num'))
|
||||
.inContext('Object'),
|
||||
]);
|
||||
});
|
||||
|
||||
group('Refutability:', () {
|
||||
test('When matched type is a subtype of variable type', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
x
|
||||
.pattern()
|
||||
.assign(expr('int'))
|
||||
.checkIr('patternAssignment(expr(int), assignedVarPattern(x))')
|
||||
.stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('When matched type is dynamic', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
x
|
||||
.pattern()
|
||||
.assign(expr('dynamic'))
|
||||
.checkIr(
|
||||
'patternAssignment(expr(dynamic), assignedVarPattern(x))')
|
||||
.stmt,
|
||||
]);
|
||||
});
|
||||
|
||||
test('When matched type is not a subtype of variable type', () {
|
||||
var x = Var('x');
|
||||
h.run([
|
||||
declare(x, type: 'num'),
|
||||
((x.pattern()..errorId = 'PATTERN').assign(expr('String'))
|
||||
..errorId = 'CONTEXT')
|
||||
.stmt,
|
||||
], expectedErrors: {
|
||||
'patternTypeMismatchInIrrefutableContext(pattern: PATTERN, '
|
||||
'context: CONTEXT, matchedType: String, requiredType: num)'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Record:', () {
|
||||
group('Positional:', () {
|
||||
group('Match dynamic:', () {
|
||||
|
|
|
@ -3623,7 +3623,8 @@ class DeclaredVariablePatternImpl extends DartPatternImpl
|
|||
|
||||
@override
|
||||
DartType computePatternSchema(ResolverVisitor resolverVisitor) {
|
||||
return resolverVisitor.analyzeVariablePatternSchema(type?.typeOrThrow);
|
||||
return resolverVisitor
|
||||
.analyzeDeclaredVariablePatternSchema(type?.typeOrThrow);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3632,7 +3633,7 @@ class DeclaredVariablePatternImpl extends DartPatternImpl
|
|||
DartType matchedType,
|
||||
SharedMatchContext context,
|
||||
) {
|
||||
resolverVisitor.analyzeVariablePattern(matchedType, context, this,
|
||||
resolverVisitor.analyzeDeclaredVariablePattern(matchedType, context, this,
|
||||
declaredElement, declaredElement?.name, type?.typeOrThrow);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
|
|||
as shared;
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart'
|
||||
as shared;
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart';
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/syntactic_entity.dart';
|
||||
|
@ -487,6 +486,10 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
|||
@override
|
||||
DartType get objectQuestionType => typeSystem.objectQuestion;
|
||||
|
||||
@override
|
||||
Operations<PromotableElement, DartType> get operations =>
|
||||
flowAnalysis.typeOperations;
|
||||
|
||||
/// Gets the current depth of the [_rewriteStack]. This may be used in
|
||||
/// assertions to verify that pushes and pops are properly balanced.
|
||||
int get rewriteStackDepth => _rewriteStack.length;
|
||||
|
@ -501,9 +504,6 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
|||
return _thisType;
|
||||
}
|
||||
|
||||
@override
|
||||
TypeOperations<DartType> get typeOperations => flowAnalysis.typeOperations;
|
||||
|
||||
@override
|
||||
DartType get unknownType => UnknownInferredType.instance;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
|
|||
hide NamedType, RecordType;
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
|
||||
as shared;
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart';
|
||||
import 'package:_fe_analyzer_shared/src/util/link.dart';
|
||||
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
|
@ -98,7 +97,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
|||
Class? mapEntryClass;
|
||||
|
||||
@override
|
||||
final TypeOperations<DartType> typeOperations;
|
||||
final Operations<VariableDeclaration, DartType> operations;
|
||||
|
||||
/// Context information for the current closure, or `null` if we are not
|
||||
/// inside a closure.
|
||||
|
@ -133,7 +132,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
|||
helper: helper, uriForInstrumentation: uriForInstrumentation);
|
||||
|
||||
InferenceVisitorImpl(
|
||||
TypeInferrerImpl inferrer, InferenceHelper helper, this.typeOperations)
|
||||
TypeInferrerImpl inferrer, InferenceHelper helper, this.operations)
|
||||
: options = new TypeAnalyzerOptions(
|
||||
nullSafetyEnabled: inferrer.libraryBuilder.isNonNullableByDefault,
|
||||
patternsEnabled: false),
|
||||
|
@ -8816,8 +8815,8 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
|||
required DartType matchedType,
|
||||
required SharedMatchContext context,
|
||||
}) {
|
||||
DartType inferredType = analyzeVariablePattern(matchedType, context, binder,
|
||||
binder.variable, binder.variable.name, binder.type);
|
||||
DartType inferredType = analyzeDeclaredVariablePattern(matchedType, context,
|
||||
binder, binder.variable, binder.variable.name, binder.type);
|
||||
instrumentation?.record(uriForInstrumentation, binder.variable.fileOffset,
|
||||
'type', new InstrumentationValueForType(inferredType));
|
||||
if (binder.type == null) {
|
||||
|
|
|
@ -182,7 +182,8 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
|
|||
|
||||
/// Provides access to the [OperationsCfe] object. This is needed by
|
||||
/// [isAssignable].
|
||||
OperationsCfe get operations => _inferrer.operations;
|
||||
Operations<VariableDeclaration, DartType> get operations =>
|
||||
_inferrer.operations;
|
||||
|
||||
TypeSchemaEnvironment get typeSchemaEnvironment =>
|
||||
_inferrer.typeSchemaEnvironment;
|
||||
|
|
Loading…
Reference in a new issue