| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import '../flow_analysis/flow_analysis.dart'; |
| import '../types/shared_type.dart'; |
| import 'type_analysis_result.dart'; |
| import 'type_analyzer_operations.dart'; |
| |
| /// Information supplied by the client to [TypeAnalyzer.analyzeSwitchExpression] |
| /// or [TypeAnalyzer.analyzeSwitchStatement] about a single case head or |
| /// `default` clause. |
| /// |
| /// The client is free to `implement` or `extend` this class. |
| class CaseHeadOrDefaultInfo<Node extends Object, Expression extends Node, |
| Variable extends Object> { |
| /// For a `case` clause, the case pattern. For a `default` clause, `null`. |
| final Node? pattern; |
| |
| /// The pattern variables declared in [pattern]. Some of them are joins of |
| /// individual pattern variable declarations. We don't know their types |
| /// until we do type analysis. So, some of these variables might become |
| /// not consistent. |
| final Map<String, Variable> variables; |
| |
| /// For a `case` clause that has a guard clause, the expression following |
| /// `when`. Otherwise `null`. |
| final Expression? guard; |
| |
| CaseHeadOrDefaultInfo({ |
| required this.pattern, |
| required this.variables, |
| this.guard, |
| }); |
| } |
| |
| /// The kind of inconsistency identified for a variable. |
| enum JoinedPatternVariableInconsistency { |
| /// No inconsistency. |
| none(0), |
| |
| /// Only one branch of a logical-or pattern has the variable. |
| logicalOr(4), |
| |
| /// Not every case of a shared case scope has the variable. |
| sharedCaseAbsent(3), |
| |
| /// The shared case scope has a label or `default` case. |
| sharedCaseHasLabel(2), |
| |
| /// The finality or type of the variable components is not the same. |
| /// This is reported for both logical-or and shared cases. |
| differentFinalityOrType(1); |
| |
| final int _severity; |
| |
| const JoinedPatternVariableInconsistency(this._severity); |
| |
| /// Returns the most serious inconsistency for `this` or [other]. |
| JoinedPatternVariableInconsistency maxWith( |
| JoinedPatternVariableInconsistency other, |
| ) { |
| return _severity > other._severity ? this : other; |
| } |
| |
| /// Returns the most serious inconsistency for `this` or [others]. |
| JoinedPatternVariableInconsistency maxWithAll( |
| Iterable<JoinedPatternVariableInconsistency> others, |
| ) { |
| JoinedPatternVariableInconsistency result = this; |
| for (JoinedPatternVariableInconsistency other in others) { |
| result = result.maxWith(other); |
| } |
| return result; |
| } |
| } |
| |
| /// The location where the join of a pattern variable happens. |
| enum JoinedPatternVariableLocation { |
| /// A single pattern, from `logical-or` patterns. |
| singlePattern, |
| |
| /// A shared `case` scope, when multiple `case`s share the same body. |
| sharedCaseScope, |
| } |
| |
| class MapPatternEntry<Expression extends Object, Pattern extends Object> { |
| final Expression key; |
| final Pattern value; |
| |
| MapPatternEntry({ |
| required this.key, |
| required this.value, |
| }); |
| } |
| |
| /// Information supplied by the client to [TypeAnalyzer.analyzeObjectPattern], |
| /// [TypeAnalyzer.analyzeRecordPattern], or |
| /// [TypeAnalyzer.analyzeRecordPatternSchema] about a single field in a record |
| /// or object pattern. |
| /// |
| /// The client is free to `implement` or `extend` this class. |
| class RecordPatternField<Node extends Object, Pattern extends Object> { |
| /// The client specific node from which this object was created. It can be |
| /// used for error reporting. |
| final Node node; |
| |
| /// If not `null` then the field is named, otherwise it is positional. |
| final String? name; |
| final Pattern pattern; |
| |
| RecordPatternField({ |
| required this.node, |
| required this.name, |
| required this.pattern, |
| }); |
| } |
| |
| /// Kinds of relational pattern operators that shared analysis needs to |
| /// distinguish. |
| enum RelationalOperatorKind { |
| /// The operator `==` |
| equals, |
| |
| /// The operator `!=` |
| notEquals, |
| |
| /// Any relational pattern operator other than `==` or `!=` |
| other, |
| } |
| |
| /// Information about a relational operator. |
| class RelationalOperatorResolution< |
| TypeStructure extends SharedTypeStructure<TypeStructure>> { |
| final RelationalOperatorKind kind; |
| final SharedTypeView<TypeStructure> parameterType; |
| final SharedTypeView<TypeStructure> returnType; |
| |
| RelationalOperatorResolution({ |
| required this.kind, |
| required this.parameterType, |
| required this.returnType, |
| }); |
| } |
| |
| /// Information supplied by the client to [TypeAnalyzer.analyzeSwitchExpression] |
| /// about an individual `case` or `default` clause. |
| /// |
| /// The client is free to `implement` or `extend` this class. |
| class SwitchExpressionMemberInfo<Node extends Object, Expression extends Node, |
| Variable extends Object> { |
| /// The [CaseHeadOrDefaultInfo] associated with this clause. |
| final CaseHeadOrDefaultInfo<Node, Expression, Variable> head; |
| |
| /// The body of the `case` or `default` clause. |
| final Expression expression; |
| |
| SwitchExpressionMemberInfo({required this.head, required this.expression}); |
| } |
| |
| /// Information supplied by the client to [TypeAnalyzer.analyzeSwitchStatement] |
| /// about an individual `case` or `default` clause. |
| /// |
| /// The client is free to `implement` or `extend` this class. |
| class SwitchStatementMemberInfo<Node extends Object, Statement extends Node, |
| Expression extends Node, Variable extends Object> { |
| /// The list of case heads for this case. |
| /// |
| /// The reason this is a list rather than a single head is because the front |
| /// end merges together cases that share a body at parse time. |
| final List<CaseHeadOrDefaultInfo<Node, Expression, Variable>> heads; |
| |
| /// Is `true` if the group of `case` and `default` clauses has a label. |
| final bool hasLabels; |
| |
| /// The statements following this `case` or `default` clause. If this list is |
| /// empty, and this is not the last `case` or `default` clause, this clause |
| /// will be considered to share a body with the `case` or `default` clause |
| /// that follows. |
| final List<Statement> body; |
| |
| /// The merged set of pattern variables from [heads]. If there is more than |
| /// one element in [heads], these variables are joins of individual pattern |
| /// variable declarations. Some of these variables might be already not |
| /// consistent, because they are present not in every head. We don't know |
| /// their types until we do type analysis. So, some of these variables |
| /// might become not consistent. |
| final Map<String, Variable> variables; |
| |
| SwitchStatementMemberInfo( |
| {required this.heads, |
| required this.body, |
| required this.variables, |
| required this.hasLabels}); |
| } |
| |
| /// Type analysis logic to be shared between the analyzer and front end. The |
| /// intention is that the client's main type inference visitor class can include |
| /// this mix-in and call shared analysis logic as needed. |
| /// |
| /// Concrete methods in this mixin, typically named `analyzeX` for some `X`, |
| /// are intended to be called by the client in order to analyze an AST node (or |
| /// equivalent) of type `X`; a client's `visit` method shouldn't have to do much |
| /// than call the corresponding `analyze` method, passing in AST node's children |
| /// and other properties, possibly take some client-specific actions with the |
| /// returned value (such as storing intermediate inference results), and then |
| /// return the returned value up the call stack. |
| /// |
| /// Abstract methods in this mixin are intended to be implemented by the client; |
| /// these are called by the `analyzeX` methods to report analysis results, to |
| /// query the client-specific information (e.g. to obtain the client's |
| /// representation of core types), and to trigger recursive analysis of child |
| /// AST nodes. |
| /// |
| /// Note that calling an `analyzeX` method is guaranteed to call `dispatch` on |
| /// all its subexpressions. However, we don't specify the precise order in |
| /// which this will happen, nor do we always specify which callbacks will be |
| /// invoked during analysis, because these details are considered part of the |
| /// implementation of type analysis, not its API. Instead, we specify the |
| /// effect that each method has on a conceptual "stack" of entities. |
| /// |
| /// In documentation, the entities in the stack are listed in low-to-high order. |
| /// So, for example, if the documentation says the stack contains "(K, L)", then |
| /// an entity of kind L is on the top of the stack, with an entity of kind K |
| /// under it. This low-to-high order is used when describing pushes and pops |
| /// too, so, for example a method documented with "pushes (K, L)" pushes K |
| /// first, then L, whereas a method documented with "pops (K, L)" pops L first, |
| /// then K. |
| /// |
| /// In the paragraph above, "K" and "L" are just variables for illustrating the |
| /// conventions. The actual kinds used by the analyzer are concepts from the |
| /// language itself such as "Statement", "Expression", "Pattern", etc. See the |
| /// `Kind` enum in `test/mini_ir.dart` for a discussion of all possible kinds of |
| /// stack entries. |
| /// |
| /// If multiple stack entries share a kind, we will sometimes add a name to |
| /// clarify which stack entry is which, e.g. analyzeIfStatement pushes |
| /// "(Expression condition, Statement ifTrue, Statement ifFalse)". |
| /// |
| /// We'll also use the convention that "n * K" represents n consecutive entities |
| /// in the stack, each with kind K. |
| /// |
| /// The kind associated with all pushes and pops is statically known (and |
| /// documented, and unit tested), and entities never change from one kind to |
| /// another. This fact gives the client considerable freedom in how to actually |
| /// represent the stack in practice; for example, they might choose to ignore |
| /// some kinds entirely, or represent certain kinds with a block of multiple |
| /// stack entries instead of just one. Or they might choose to multiple stacks, |
| /// one for each kind. It's also possible that some clients won't need to keep |
| /// a stack at all. |
| /// |
| /// Reasons a client might want to actually have a stack include: |
| /// - Constructing a lowered intermediate representation of the code as a side |
| /// effect of analysis, |
| /// - Building up a symbolic representation of the program's runtime behavior, |
| /// - Or keeping track of AST nodes that need to be replaced (e.g. replacing an |
| /// `integer literal` node with a `double literal` node when int->double |
| /// conversion happens). |
| /// |
| /// The unit tests in the `_fe_analyzer_shared` package associate a simple |
| /// intermediate representation with each stack entry, and also record the kind |
| /// of each entry in order to verify that when an entity is popped, it has the |
| /// expected kind. |
| mixin TypeAnalyzer< |
| TypeStructure extends SharedTypeStructure<TypeStructure>, |
| Node extends Object, |
| Statement extends Node, |
| Expression extends Node, |
| Variable extends Object, |
| Pattern extends Node, |
| Error, |
| TypeParameterStructure extends SharedTypeParameterStructure<TypeStructure>, |
| TypeDeclarationType extends Object, |
| TypeDeclaration extends Object> { |
| TypeAnalyzerErrors<Node, Statement, Expression, Variable, |
| SharedTypeView<TypeStructure>, Pattern, Error> get errors; |
| |
| /// Returns the client's [FlowAnalysis] object. |
| FlowAnalysis<Node, Statement, Expression, Variable, |
| SharedTypeView<TypeStructure>> get flow; |
| |
| /// The [TypeAnalyzerOperations], used to access types, check subtyping, and |
| /// query variable types. |
| TypeAnalyzerOperations<TypeStructure, Variable, TypeParameterStructure, |
| TypeDeclarationType, TypeDeclaration> get operations; |
| |
| /// Options affecting the behavior of [TypeAnalyzer]. |
| TypeAnalyzerOptions get options; |
| |
| /// Analyzes a non-wildcard variable pattern appearing in an assignment |
| /// context. [node] is the pattern itself, and [variable] is the variable |
| /// being referenced. |
| /// |
| /// Returns an [AssignedVariablePatternResult] with information about reported |
| /// errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// For wildcard patterns in an assignment context, |
| /// [analyzeDeclaredVariablePattern] should be used instead. |
| /// |
| /// Stack effect: none. |
| AssignedVariablePatternResult<TypeStructure, Error> |
| analyzeAssignedVariablePattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Variable variable) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| Error? duplicateAssignmentPatternVariableError; |
| Map<Variable, Pattern>? assignedVariables = context.assignedVariables; |
| if (assignedVariables != null) { |
| Pattern? original = assignedVariables[variable]; |
| if (original == null) { |
| assignedVariables[variable] = node; |
| } else { |
| duplicateAssignmentPatternVariableError = |
| errors.duplicateAssignmentPatternVariable( |
| variable: variable, |
| original: original, |
| duplicate: node, |
| ); |
| } |
| } |
| |
| SharedTypeView<TypeStructure> variableDeclaredType = |
| operations.variableType(variable); |
| Node? irrefutableContext = context.irrefutableContext; |
| assert(irrefutableContext != null, |
| 'Assigned variables must only appear in irrefutable pattern contexts'); |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| matchedValueType is! SharedDynamicTypeStructure && |
| matchedValueType is! SharedInvalidTypeStructure && |
| !operations.isSubtypeOf(matchedValueType, variableDeclaredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: variableDeclaredType); |
| } |
| flow.promoteForPattern( |
| matchedType: matchedValueType, knownType: variableDeclaredType); |
| flow.assignedVariablePattern(node, variable, matchedValueType); |
| return new AssignedVariablePatternResult( |
| duplicateAssignmentPatternVariableError: |
| duplicateAssignmentPatternVariableError, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a variable pattern appearing in an assignment |
| /// context. [variable] is the variable being referenced. |
| SharedTypeSchemaView<TypeStructure> analyzeAssignedVariablePatternSchema( |
| Variable variable) => |
| operations.typeToSchema( |
| flow.promotedType(variable) ?? operations.variableType(variable)); |
| |
| /// Analyzes a cast pattern. [innerPattern] is the sub-pattern] and |
| /// [requiredType] is the type to cast to. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (Pattern innerPattern). |
| PatternResult<TypeStructure> analyzeCastPattern({ |
| required MatchContext<Node, Expression, Pattern, |
| SharedTypeView<TypeStructure>, Variable> |
| context, |
| required Pattern pattern, |
| required Pattern innerPattern, |
| required SharedTypeView<TypeStructure> requiredType, |
| }) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, |
| knownType: requiredType, |
| matchFailsIfWrongType: false); |
| if (operations.isSubtypeOf(matchedValueType, requiredType) && |
| requiredType is! SharedInvalidTypeStructure) { |
| errors.matchedTypeIsSubtypeOfRequired( |
| pattern: pattern, |
| matchedType: matchedValueType, |
| requiredType: requiredType, |
| ); |
| } |
| // Note: although technically the inner pattern match of a cast-pattern |
| // operates on the same value as the cast pattern does, we analyze it as |
| // though it's a different value; this ensures that (a) the matched value |
| // type when matching the inner pattern is precisely the cast type, and (b) |
| // promotions triggered by the inner pattern have no effect outside the |
| // cast. |
| flow.pushSubpattern(requiredType); |
| dispatchPattern(context.withUnnecessaryWildcardKind(null), innerPattern); |
| // Stack: (Pattern) |
| flow.popSubpattern(); |
| return new PatternResult(matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a cast pattern. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeCastPatternSchema() => |
| operations.unknownType; |
| |
| /// Analyzes a constant pattern. [node] is the pattern itself, and |
| /// [expression] is the constant expression. Depending on the client's |
| /// representation, [node] and [expression] might or might not be identical. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Returns a [ConstantPatternResult] with the static type of [expression] |
| /// and information about reported errors. |
| /// |
| /// Stack effect: pushes (Expression). |
| ConstantPatternResult<TypeStructure, Error> analyzeConstantPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Node node, |
| Expression expression) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| // Stack: () |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? refutablePatternInIrrefutableContextError; |
| if (irrefutableContext != null) { |
| refutablePatternInIrrefutableContextError = |
| errors.refutablePatternInIrrefutableContext( |
| pattern: node, context: irrefutableContext); |
| } |
| SharedTypeView<TypeStructure> expressionType = analyzeExpression( |
| expression, operations.typeToSchema(matchedValueType)); |
| flow.constantPattern_end(expression, expressionType, |
| patternsEnabled: options.patternsEnabled, |
| matchedValueType: matchedValueType); |
| // Stack: (Expression) |
| Error? caseExpressionTypeMismatchError; |
| if (!options.patternsEnabled) { |
| Expression? switchScrutinee = context.switchScrutinee; |
| if (switchScrutinee != null) { |
| bool nullSafetyEnabled = options.nullSafetyEnabled; |
| bool matches = nullSafetyEnabled |
| ? operations.isSubtypeOf(expressionType, matchedValueType) |
| : operations.isAssignableTo(expressionType, matchedValueType); |
| if (!matches) { |
| caseExpressionTypeMismatchError = errors.caseExpressionTypeMismatch( |
| caseExpression: expression, |
| scrutinee: switchScrutinee, |
| caseExpressionType: expressionType, |
| scrutineeType: matchedValueType, |
| nullSafetyEnabled: nullSafetyEnabled); |
| } |
| } |
| } |
| return new ConstantPatternResult( |
| expressionType: expressionType, |
| refutablePatternInIrrefutableContextError: |
| refutablePatternInIrrefutableContextError, |
| caseExpressionTypeMismatchError: caseExpressionTypeMismatchError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a constant pattern. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeConstantPatternSchema() { |
| // 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. |
| errors.assertInErrorRecovery(); |
| return operations.unknownType; |
| } |
| |
| /// Analyzes a variable pattern in a non-assignment context. [node] is the |
| /// pattern itself, [variable] is the variable, [declaredType] is the |
| /// explicitly declared type (if present). [variableName] is the name of the |
| /// variable; this is used to match up corresponding variables in the |
| /// different branches of logical-or patterns, as well as different switch |
| /// cases that share a body. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Returns a [DeclaredVariablePatternResult] with the static type of the |
| /// variable (possibly inferred) and information about reported errors. |
| /// |
| /// Stack effect: none. |
| DeclaredVariablePatternResult<TypeStructure, Error> |
| analyzeDeclaredVariablePattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Variable variable, |
| String variableName, |
| SharedTypeView<TypeStructure>? declaredType, |
| ) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| SharedTypeView<TypeStructure> staticType = |
| declaredType ?? variableTypeFromInitializerType(matchedValueType); |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| matchedValueType is! SharedDynamicTypeStructure && |
| matchedValueType is! SharedInvalidTypeStructure && |
| !operations.isSubtypeOf(matchedValueType, staticType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: staticType); |
| } |
| flow.promoteForPattern( |
| matchedType: matchedValueType, knownType: staticType); |
| // The promotion may have made the matched type even more specific than |
| // either `matchedType` or `staticType`, so fetch it again and use that |
| // in the call to `declaredVariablePattern` below. |
| SharedTypeView<TypeStructure> promotedValueType = |
| flow.getMatchedValueType(); |
| bool isImplicitlyTyped = declaredType == null; |
| // TODO(paulberry): are we handling _isFinal correctly? |
| int promotionKey = context.patternVariablePromotionKeys[variableName] = |
| flow.declaredVariablePattern( |
| matchedType: promotedValueType, |
| staticType: staticType, |
| isFinal: context.isFinal || operations.isVariableFinal(variable), |
| isLate: false, |
| isImplicitlyTyped: isImplicitlyTyped); |
| setVariableType(variable, staticType); |
| (context.componentVariables[variableName] ??= []).add(variable); |
| flow.assignMatchedPatternVariable(variable, promotionKey); |
| return new DeclaredVariablePatternResult( |
| staticType: staticType, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// 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. |
| SharedTypeSchemaView<TypeStructure> analyzeDeclaredVariablePatternSchema( |
| SharedTypeView<TypeStructure>? declaredType) { |
| return declaredType == null |
| ? operations.unknownType |
| : operations.typeToSchema(declaredType); |
| } |
| |
| /// Analyzes an expression. [node] is the expression to analyze, and |
| /// [schema] is the type schema which should be used for type inference. |
| /// |
| /// Stack effect: pushes (Expression). |
| SharedTypeView<TypeStructure> analyzeExpression( |
| Expression node, SharedTypeSchemaView<TypeStructure> schema) { |
| // Stack: () |
| if (schema is SharedDynamicTypeSchemaView<TypeStructure>) { |
| schema = operations.unknownType; |
| } |
| ExpressionTypeAnalysisResult<TypeStructure> result = |
| dispatchExpression(node, schema); |
| // Stack: (Expression) |
| if (operations.isNever(result.provisionalType)) { |
| flow.handleExit(); |
| } |
| return result.resolveShorting(); |
| } |
| |
| /// Analyzes a collection element of the form |
| /// `if (expression case pattern) ifTrue` or |
| /// `if (expression case pattern) ifTrue else ifFalse`. |
| /// |
| /// [node] should be the AST node for the entire element, [expression] for |
| /// the expression, [pattern] for the pattern to match, [ifTrue] for the |
| /// "then" branch, and [ifFalse] for the "else" branch (if present). |
| /// |
| /// [variables] should be a map from variable name to the variable the client |
| /// wishes to use to represent that variable. This is used to join together |
| /// variables that appear in different branches of logical-or patterns. |
| /// |
| /// Returns a [IfCaseStatementResult] with the static type of [expression] and |
| /// information about reported errors. |
| /// |
| /// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard, |
| /// CollectionElement ifTrue, CollectionElement ifFalse). If there is no |
| /// `else` clause, the representation for `ifFalse` will be pushed by |
| /// [handleNoCollectionElement]. If there is no guard, the representation |
| /// for `guard` will be pushed by [handleNoGuard]. |
| IfCaseStatementResult<TypeStructure, Error> analyzeIfCaseElement({ |
| required Node node, |
| required Expression expression, |
| required Pattern pattern, |
| required Map<String, Variable> variables, |
| required Expression? guard, |
| required Node ifTrue, |
| required Node? ifFalse, |
| required Object? context, |
| }) { |
| // Stack: () |
| flow.ifCaseStatement_begin(); |
| SharedTypeView<TypeStructure> initializerType = |
| analyzeExpression(expression, operations.unknownType); |
| flow.ifCaseStatement_afterExpression(expression, initializerType); |
| // Stack: (Expression) |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| // TODO(paulberry): rework handling of isFinal |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable>( |
| isFinal: false, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| // Stack: (Expression, Pattern) |
| _finishJoinedPatternVariables( |
| variables, componentVariables, patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.singlePattern); |
| Error? nonBooleanGuardError; |
| SharedTypeView<TypeStructure>? guardType; |
| if (guard != null) { |
| guardType = analyzeExpression( |
| guard, operations.typeToSchema(operations.boolType)); |
| nonBooleanGuardError = _checkGuardType(guard, guardType); |
| } else { |
| handleNoGuard(node, 0); |
| } |
| // Stack: (Expression, Pattern, Guard) |
| flow.ifCaseStatement_thenBegin(guard); |
| _analyzeIfElementCommon(node, ifTrue, ifFalse, context); |
| return new IfCaseStatementResult( |
| matchedExpressionType: initializerType, |
| nonBooleanGuardError: nonBooleanGuardError, |
| guardType: guardType); |
| } |
| |
| /// Analyzes a statement of the form `if (expression case pattern) ifTrue` or |
| /// `if (expression case pattern) ifTrue else ifFalse`. |
| /// |
| /// [node] should be the AST node for the entire statement, [expression] for |
| /// the expression, [pattern] for the pattern to match, [ifTrue] for the |
| /// "then" branch, and [ifFalse] for the "else" branch (if present). |
| /// |
| /// Returns a [IfCaseStatementResult] with the static type of [expression] and |
| /// information about reported errors. |
| /// |
| /// Stack effect: pushes (Expression scrutinee, Pattern, Expression guard, |
| /// Statement ifTrue, Statement ifFalse). If there is no `else` clause, the |
| /// representation for `ifFalse` will be pushed by [handleNoStatement]. If |
| /// there is no guard, the representation for `guard` will be pushed by |
| /// [handleNoGuard]. |
| IfCaseStatementResult<TypeStructure, Error> analyzeIfCaseStatement( |
| Statement node, |
| Expression expression, |
| Pattern pattern, |
| Expression? guard, |
| Statement ifTrue, |
| Statement? ifFalse, |
| Map<String, Variable> variables, |
| ) { |
| // Stack: () |
| flow.ifCaseStatement_begin(); |
| SharedTypeView<TypeStructure> initializerType = |
| analyzeExpression(expression, operations.unknownType); |
| flow.ifCaseStatement_afterExpression(expression, initializerType); |
| // Stack: (Expression) |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| // TODO(paulberry): rework handling of isFinal |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable>( |
| isFinal: false, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| |
| _finishJoinedPatternVariables( |
| variables, |
| componentVariables, |
| patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.singlePattern, |
| ); |
| |
| handle_ifCaseStatement_afterPattern(node: node); |
| // Stack: (Expression, Pattern) |
| Error? nonBooleanGuardError; |
| SharedTypeView<TypeStructure>? guardType; |
| if (guard != null) { |
| guardType = analyzeExpression( |
| guard, operations.typeToSchema(operations.boolType)); |
| nonBooleanGuardError = _checkGuardType(guard, guardType); |
| } else { |
| handleNoGuard(node, 0); |
| } |
| // Stack: (Expression, Pattern, Guard) |
| flow.ifCaseStatement_thenBegin(guard); |
| _analyzeIfCommon(node, ifTrue, ifFalse); |
| return new IfCaseStatementResult( |
| matchedExpressionType: initializerType, |
| nonBooleanGuardError: nonBooleanGuardError, |
| guardType: guardType); |
| } |
| |
| /// Analyzes a collection element of the form `if (condition) ifTrue` or |
| /// `if (condition) ifTrue else ifFalse`. |
| /// |
| /// [node] should be the AST node for the entire element, [condition] for |
| /// the condition expression, [ifTrue] for the "then" branch, and [ifFalse] |
| /// for the "else" branch (if present). |
| /// |
| /// Stack effect: pushes (Expression condition, CollectionElement ifTrue, |
| /// CollectionElement ifFalse). Note that if there is no `else` clause, the |
| /// representation for `ifFalse` will be pushed by |
| /// [handleNoCollectionElement]. |
| void analyzeIfElement({ |
| required Node node, |
| required Expression condition, |
| required Node ifTrue, |
| required Node? ifFalse, |
| required Object? context, |
| }) { |
| // Stack: () |
| flow.ifStatement_conditionBegin(); |
| analyzeExpression(condition, operations.typeToSchema(operations.boolType)); |
| handle_ifElement_conditionEnd(node); |
| // Stack: (Expression condition) |
| flow.ifStatement_thenBegin(condition, node); |
| _analyzeIfElementCommon(node, ifTrue, ifFalse, context); |
| } |
| |
| /// Analyzes a statement of the form `if (condition) ifTrue` or |
| /// `if (condition) ifTrue else ifFalse`. |
| /// |
| /// [node] should be the AST node for the entire statement, [condition] for |
| /// the condition expression, [ifTrue] for the "then" branch, and [ifFalse] |
| /// for the "else" branch (if present). |
| /// |
| /// Stack effect: pushes (Expression condition, Statement ifTrue, Statement |
| /// ifFalse). Note that if there is no `else` clause, the representation for |
| /// `ifFalse` will be pushed by [handleNoStatement]. |
| void analyzeIfStatement(Statement node, Expression condition, |
| Statement ifTrue, Statement? ifFalse) { |
| // Stack: () |
| flow.ifStatement_conditionBegin(); |
| analyzeExpression(condition, operations.typeToSchema(operations.boolType)); |
| handle_ifStatement_conditionEnd(node); |
| // Stack: (Expression condition) |
| flow.ifStatement_thenBegin(condition, node); |
| _analyzeIfCommon(node, ifTrue, ifFalse); |
| } |
| |
| /// Analyzes an integer literal, given the type schema [schema]. |
| /// |
| /// Stack effect: none. |
| IntTypeAnalysisResult<TypeStructure> analyzeIntLiteral( |
| SharedTypeSchemaView<TypeStructure> schema) { |
| bool convertToDouble = !operations.isTypeSchemaSatisfied( |
| type: operations.intType, typeSchema: schema) && |
| operations.isTypeSchemaSatisfied( |
| type: operations.doubleType, typeSchema: schema); |
| SharedTypeView<TypeStructure> type = |
| convertToDouble ? operations.doubleType : operations.intType; |
| return new IntTypeAnalysisResult<TypeStructure>( |
| type: type, convertedToDouble: convertToDouble); |
| } |
| |
| /// Analyzes a list pattern. [node] is the pattern itself, [elementType] is |
| /// the list element type (if explicitly supplied), and [elements] is the |
| /// list of subpatterns. |
| /// |
| /// Returns a [ListPatternResult] with the required type and information about |
| /// reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (n * Pattern) where n = elements.length. |
| ListPatternResult<TypeStructure, Error> analyzeListPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| {SharedTypeView<TypeStructure>? elementType, |
| required List<Node> elements}) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| SharedTypeView<TypeStructure> valueType; |
| if (elementType != null) { |
| valueType = elementType; |
| } else { |
| SharedTypeView<TypeStructure>? listElementType = |
| operations.matchListType(matchedValueType); |
| if (listElementType != null) { |
| valueType = listElementType; |
| } else if (matchedValueType is SharedDynamicTypeStructure) { |
| valueType = operations.dynamicType; |
| } else if (matchedValueType is SharedInvalidTypeStructure) { |
| valueType = operations.errorType; |
| } else { |
| valueType = operations.objectQuestionType; |
| } |
| } |
| SharedTypeView<TypeStructure> requiredType = operations.listType(valueType); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, |
| knownType: requiredType, |
| matchMayFailEvenIfCorrectType: |
| !(elements.length == 1 && isRestPatternElement(elements[0]))); |
| // Stack: () |
| Node? previousRestPattern; |
| Map<int, Error>? duplicateRestPatternErrors; |
| for (int i = 0; i < elements.length; i++) { |
| Node element = elements[i]; |
| if (isRestPatternElement(element)) { |
| if (previousRestPattern != null) { |
| (duplicateRestPatternErrors ??= {})[i] = errors.duplicateRestPattern( |
| mapOrListPattern: node, |
| original: previousRestPattern, |
| duplicate: element, |
| ); |
| } |
| previousRestPattern = element; |
| Pattern? subPattern = getRestPatternElementPattern(element); |
| if (subPattern != null) { |
| SharedTypeView<TypeStructure> subPatternMatchedType = requiredType; |
| flow.pushSubpattern(subPatternMatchedType); |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), subPattern); |
| flow.popSubpattern(); |
| } |
| handleListPatternRestElement(node, element); |
| } else { |
| flow.pushSubpattern(valueType); |
| dispatchPattern(context.withUnnecessaryWildcardKind(null), element); |
| flow.popSubpattern(); |
| } |
| } |
| // Stack: (n * Pattern) where n = elements.length |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| !operations.isAssignableTo(matchedValueType, requiredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: requiredType); |
| } |
| return new ListPatternResult( |
| requiredType: requiredType, |
| duplicateRestPatternErrors: duplicateRestPatternErrors, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// 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. |
| SharedTypeSchemaView<TypeStructure> analyzeListPatternSchema({ |
| required SharedTypeView<TypeStructure>? elementType, |
| required List<Node> elements, |
| }) { |
| if (elementType != null) { |
| return operations.listTypeSchema(operations.typeToSchema(elementType)); |
| } |
| |
| if (elements.isEmpty) { |
| return operations.listTypeSchema(operations.unknownType); |
| } |
| |
| SharedTypeSchemaView<TypeStructure>? currentGLB; |
| for (Node element in elements) { |
| SharedTypeSchemaView<TypeStructure>? typeToAdd; |
| if (isRestPatternElement(element)) { |
| Pattern? subPattern = getRestPatternElementPattern(element); |
| if (subPattern != null) { |
| SharedTypeSchemaView<TypeStructure> subPatternType = |
| dispatchPatternSchema(subPattern); |
| typeToAdd = operations.matchIterableTypeSchema(subPatternType); |
| } |
| } else { |
| typeToAdd = dispatchPatternSchema(element); |
| } |
| if (typeToAdd != null) { |
| if (currentGLB == null) { |
| currentGLB = typeToAdd; |
| } else { |
| currentGLB = operations.typeSchemaGlb(currentGLB, typeToAdd); |
| } |
| } |
| } |
| currentGLB ??= operations.unknownType; |
| return operations.listTypeSchema(currentGLB); |
| } |
| |
| /// Analyzes a logical-and pattern. [node] is the pattern itself, and [lhs] |
| /// and [rhs] are the left and right sides of the `&&` operator. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (Pattern left, Pattern right) |
| PatternResult<TypeStructure> analyzeLogicalAndPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Node lhs, |
| Node rhs) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| // Stack: () |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind( |
| UnnecessaryWildcardKind.logicalAndPatternOperand, |
| ), |
| lhs, |
| ); |
| // Stack: (Pattern left) |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind( |
| UnnecessaryWildcardKind.logicalAndPatternOperand, |
| ), |
| rhs, |
| ); |
| // Stack: (Pattern left, Pattern right) |
| return new PatternResult(matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a logical-and pattern. [lhs] and [rhs] are |
| /// the left and right sides of the `&&` operator. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeLogicalAndPatternSchema( |
| Node lhs, Node rhs) { |
| return operations.typeSchemaGlb( |
| dispatchPatternSchema(lhs), dispatchPatternSchema(rhs)); |
| } |
| |
| /// Analyzes a logical-or pattern. [node] is the pattern itself, and [lhs] |
| /// and [rhs] are the left and right sides of the `||` operator. |
| /// |
| /// Returns a [LogicalOrPatternResult] with information about reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (Pattern left, Pattern right) |
| LogicalOrPatternResult<TypeStructure, Error> analyzeLogicalOrPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Node lhs, |
| Node rhs) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? refutablePatternInIrrefutableContextError; |
| if (irrefutableContext != null) { |
| refutablePatternInIrrefutableContextError = |
| errors.refutablePatternInIrrefutableContext( |
| pattern: node, context: irrefutableContext); |
| // Avoid cascading errors |
| context = context.makeRefutable(); |
| } |
| // Stack: () |
| flow.logicalOrPattern_begin(); |
| Map<String, int> leftPromotionKeys = {}; |
| dispatchPattern( |
| context |
| .withPromotionKeys(leftPromotionKeys) |
| .withUnnecessaryWildcardKind(null), |
| lhs, |
| ); |
| // Stack: (Pattern left) |
| // We'll use the promotion keys allocated during processing of the LHS as |
| // the merged keys. |
| for (MapEntry<String, int> entry in leftPromotionKeys.entries) { |
| String variableName = entry.key; |
| int promotionKey = entry.value; |
| assert(!context.patternVariablePromotionKeys.containsKey(variableName)); |
| context.patternVariablePromotionKeys[variableName] = promotionKey; |
| } |
| flow.logicalOrPattern_afterLhs(); |
| handle_logicalOrPattern_afterLhs(node); |
| Map<String, int> rightPromotionKeys = {}; |
| dispatchPattern( |
| context |
| .withPromotionKeys(rightPromotionKeys) |
| .withUnnecessaryWildcardKind(null), |
| rhs, |
| ); |
| // Stack: (Pattern left, Pattern right) |
| for (MapEntry<String, int> entry in rightPromotionKeys.entries) { |
| String variableName = entry.key; |
| int rightPromotionKey = entry.value; |
| int? mergedPromotionKey = leftPromotionKeys[variableName]; |
| if (mergedPromotionKey == null) { |
| // No matching variable on the LHS. This is an error condition (which |
| // has already been reported by VariableBinder). For error recovery, |
| // we still need to add the variable to |
| // context.patternVariablePromotionKeys so that later analysis still |
| // accounts for the presence of this variable. So we just use the |
| // promotion key from the RHS as the merged key. |
| mergedPromotionKey = rightPromotionKey; |
| assert(!context.patternVariablePromotionKeys.containsKey(variableName)); |
| context.patternVariablePromotionKeys[variableName] = mergedPromotionKey; |
| } else { |
| // Copy the promotion data over to the merged key. |
| flow.copyPromotionData( |
| sourceKey: rightPromotionKey, destinationKey: mergedPromotionKey); |
| } |
| } |
| // Since the promotion data is now all stored in the merged keys in both |
| // flow control branches, the normal join process will combine promotions |
| // accordingly. |
| flow.logicalOrPattern_end(); |
| return new LogicalOrPatternResult( |
| refutablePatternInIrrefutableContextError: |
| refutablePatternInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a logical-or pattern. [lhs] and [rhs] are |
| /// the left and right sides of the `|` or `&` operator. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeLogicalOrPatternSchema( |
| Node lhs, Node rhs) { |
| // 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 operations.unknownType; |
| } |
| |
| /// Analyzes a map pattern. [node] is the pattern itself, [typeArguments] |
| /// contain explicit type arguments (if specified), and [elements] is the |
| /// list of subpatterns. |
| /// |
| /// Returns a [MapPatternResult] with the required type and information about |
| /// reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (n * MapPatternElement) where n = elements.length. |
| MapPatternResult<TypeStructure, Error> analyzeMapPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, { |
| required ({ |
| SharedTypeView<TypeStructure> keyType, |
| SharedTypeView<TypeStructure> valueType |
| })? typeArguments, |
| required List<Node> elements, |
| }) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| SharedTypeView<TypeStructure> keyType; |
| SharedTypeView<TypeStructure> valueType; |
| SharedTypeSchemaView<TypeStructure> keySchema; |
| if (typeArguments != null) { |
| keyType = typeArguments.keyType; |
| valueType = typeArguments.valueType; |
| keySchema = operations.typeToSchema(keyType); |
| } else { |
| typeArguments = operations.matchMapType(matchedValueType); |
| if (typeArguments != null) { |
| keyType = typeArguments.keyType; |
| valueType = typeArguments.valueType; |
| keySchema = operations.typeToSchema(keyType); |
| } else if (matchedValueType is SharedDynamicTypeStructure) { |
| keyType = operations.dynamicType; |
| valueType = operations.dynamicType; |
| keySchema = operations.unknownType; |
| } else if (matchedValueType is SharedInvalidTypeStructure) { |
| keyType = operations.errorType; |
| valueType = operations.errorType; |
| keySchema = operations.unknownType; |
| } else { |
| keyType = operations.objectQuestionType; |
| valueType = operations.objectQuestionType; |
| keySchema = operations.unknownType; |
| } |
| } |
| SharedTypeView<TypeStructure> requiredType = operations.mapType( |
| keyType: keyType, |
| valueType: valueType, |
| ); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, |
| knownType: requiredType, |
| matchMayFailEvenIfCorrectType: true); |
| // Stack: () |
| |
| Map<int, Error>? restPatternErrors; |
| for (int i = 0; i < elements.length; i++) { |
| Node element = elements[i]; |
| if (isRestPatternElement(element)) { |
| (restPatternErrors ??= {})[i] = |
| errors.restPatternInMap(node: node, element: element); |
| } |
| } |
| |
| for (int i = 0; i < elements.length; i++) { |
| Node element = elements[i]; |
| MapPatternEntry<Expression, Pattern>? entry = getMapPatternEntry(element); |
| if (entry != null) { |
| SharedTypeView<TypeStructure> keyType = |
| analyzeExpression(entry.key, keySchema); |
| flow.pushSubpattern(valueType); |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), |
| entry.value, |
| ); |
| handleMapPatternEntry(node, element, keyType); |
| flow.popSubpattern(); |
| } else { |
| assert(isRestPatternElement(element)); |
| Pattern? subPattern = getRestPatternElementPattern(element); |
| if (subPattern != null) { |
| flow.pushSubpattern(operations.dynamicType); |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), |
| subPattern, |
| ); |
| flow.popSubpattern(); |
| } |
| handleMapPatternRestElement(node, element); |
| } |
| } |
| // Stack: (n * MapPatternElement) where n = elements.length |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| !operations.isAssignableTo(matchedValueType, requiredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: requiredType, |
| ); |
| } |
| Error? emptyMapPatternError; |
| if (elements.isEmpty) { |
| emptyMapPatternError = errors.emptyMapPattern(pattern: node); |
| } |
| return new MapPatternResult( |
| requiredType: requiredType, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| emptyMapPatternError: emptyMapPatternError, |
| restPatternErrors: restPatternErrors, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a map pattern. [typeArguments] contain |
| /// explicit type arguments (if specified), and [elements] is the list of |
| /// subpatterns. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeMapPatternSchema({ |
| required ({ |
| SharedTypeView<TypeStructure> keyType, |
| SharedTypeView<TypeStructure> valueType |
| })? typeArguments, |
| required List<Node> elements, |
| }) { |
| if (typeArguments != null) { |
| return operations.typeToSchema(operations.mapType( |
| keyType: typeArguments.keyType, |
| valueType: typeArguments.valueType, |
| )); |
| } |
| |
| SharedTypeSchemaView<TypeStructure>? valueType; |
| for (Node element in elements) { |
| MapPatternEntry<Expression, Pattern>? entry = getMapPatternEntry(element); |
| if (entry != null) { |
| SharedTypeSchemaView<TypeStructure> entryValueType = |
| dispatchPatternSchema(entry.value); |
| if (valueType == null) { |
| valueType = entryValueType; |
| } else { |
| valueType = operations.typeSchemaGlb(valueType, entryValueType); |
| } |
| } |
| } |
| return operations.mapTypeSchema( |
| keyTypeSchema: operations.unknownType, |
| valueTypeSchema: valueType ?? operations.unknownType, |
| ); |
| } |
| |
| /// Analyzes a null-check or null-assert pattern. [node] is the pattern |
| /// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates |
| /// whether this is a null-check or a null-assert pattern. |
| /// |
| /// Returns a [NullCheckOrAssertPatternResult] with information about |
| /// reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (Pattern innerPattern). |
| NullCheckOrAssertPatternResult<TypeStructure, Error> |
| analyzeNullCheckOrAssertPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Pattern innerPattern, |
| {required bool isAssert}) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| // Stack: () |
| Error? refutablePatternInIrrefutableContextError; |
| Error? matchedTypeIsStrictlyNonNullableError; |
| Node? irrefutableContext = context.irrefutableContext; |
| bool matchedTypeIsStrictlyNonNullable = flow.nullCheckOrAssertPattern_begin( |
| isAssert: isAssert, matchedValueType: matchedValueType); |
| if (irrefutableContext != null && !isAssert) { |
| refutablePatternInIrrefutableContextError = |
| errors.refutablePatternInIrrefutableContext( |
| pattern: node, context: irrefutableContext); |
| // Avoid cascading errors |
| context = context.makeRefutable(); |
| } else if (matchedTypeIsStrictlyNonNullable) { |
| matchedTypeIsStrictlyNonNullableError = |
| errors.matchedTypeIsStrictlyNonNullable( |
| pattern: node, |
| matchedType: matchedValueType, |
| ); |
| } |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), |
| innerPattern, |
| ); |
| // Stack: (Pattern) |
| flow.nullCheckOrAssertPattern_end(); |
| |
| return new NullCheckOrAssertPatternResult( |
| refutablePatternInIrrefutableContextError: |
| refutablePatternInIrrefutableContextError, |
| matchedTypeIsStrictlyNonNullableError: |
| matchedTypeIsStrictlyNonNullableError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// 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. |
| SharedTypeSchemaView<TypeStructure> analyzeNullCheckOrAssertPatternSchema( |
| Pattern innerPattern, |
| {required bool isAssert}) { |
| if (isAssert) { |
| return operations |
| .makeTypeSchemaNullable(dispatchPatternSchema(innerPattern)); |
| } else { |
| // Null-check patterns are only allowed in refutable contexts, and |
| // refutable contexts don't propagate a type schema into the scrutinee. |
| // So this code path is only reachable if the user's code contains errors. |
| errors.assertInErrorRecovery(); |
| return operations.unknownType; |
| } |
| } |
| |
| /// Analyzes an object pattern. [node] is the pattern itself, and [fields] |
| /// is the list of subpatterns. |
| /// |
| /// Returns a [ObjectPatternResult] with the required type and information |
| /// about reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (n * Pattern) where n = fields.length. |
| ObjectPatternResult<TypeStructure, Error> analyzeObjectPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, { |
| required List<RecordPatternField<Node, Pattern>> fields, |
| }) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| Map<int, Error>? duplicateRecordPatternFieldErrors = |
| _reportDuplicateRecordPatternFields(node, fields); |
| |
| SharedTypeView<TypeStructure> requiredType = |
| downwardInferObjectPatternRequiredType( |
| matchedType: matchedValueType, |
| pattern: node, |
| ); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, knownType: requiredType); |
| |
| // If the required type is `dynamic` or `Never`, then every getter is |
| // treated as having the same type. |
| (Object?, SharedTypeView<TypeStructure>)? overridePropertyGetType; |
| if (requiredType is SharedDynamicTypeStructure || |
| requiredType is SharedInvalidTypeStructure || |
| operations.isNever(requiredType)) { |
| overridePropertyGetType = (null, requiredType); |
| } |
| |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| !operations.isAssignableTo(matchedValueType, requiredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: requiredType, |
| ); |
| } |
| |
| // Stack: () |
| for (RecordPatternField<Node, Pattern> field in fields) { |
| var ( |
| Object? propertyMember, |
| SharedTypeView<TypeStructure> unpromotedPropertyType |
| ) = overridePropertyGetType ?? |
| resolveObjectPatternPropertyGet( |
| objectPattern: node, |
| receiverType: requiredType, |
| field: field, |
| ); |
| // Note: an object pattern field must always have a property name, but in |
| // error recovery circumstances, one may be absent; when this happens, use |
| // the empty string as a the property name to prevent a crash. |
| String propertyName = field.name ?? ''; |
| SharedTypeView<TypeStructure> promotedPropertyType = |
| flow.pushPropertySubpattern( |
| propertyName, propertyMember, unpromotedPropertyType) ?? |
| unpromotedPropertyType; |
| if (operations.isNever(promotedPropertyType)) { |
| flow.handleExit(); |
| } |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), |
| field.pattern, |
| ); |
| flow.popPropertySubpattern(); |
| } |
| // Stack: (n * Pattern) where n = fields.length |
| |
| return new ObjectPatternResult( |
| requiredType: requiredType, |
| duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for an object pattern. [type] is the type |
| /// specified with the object name, and with the type arguments applied. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeObjectPatternSchema( |
| SharedTypeView<TypeStructure> type) { |
| return operations.typeToSchema(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). |
| PatternAssignmentAnalysisResult<TypeStructure> analyzePatternAssignment( |
| Expression node, Pattern pattern, Expression rhs) { |
| // Stack: () |
| SharedTypeSchemaView<TypeStructure> patternSchema = |
| dispatchPatternSchema(pattern); |
| SharedTypeView<TypeStructure> rhsType = |
| analyzeExpression(rhs, patternSchema); |
| // Stack: (Expression) |
| flow.patternAssignment_afterRhs(rhs, rhsType); |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable>( |
| isFinal: false, |
| irrefutableContext: node, |
| assignedVariables: <Variable, Pattern>{}, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| if (componentVariables.isNotEmpty) { |
| // Declared pattern variables should never appear in a pattern assignment |
| // so this should never happen. |
| errors.assertInErrorRecovery(); |
| } |
| flow.patternAssignment_end(); |
| // Stack: (Expression, Pattern) |
| return new PatternAssignmentAnalysisResult<TypeStructure>( |
| patternSchema: patternSchema, |
| type: rhsType, |
| ); |
| } |
| |
| /// Analyzes a `pattern-for-in` statement or element. |
| /// |
| /// Statement: |
| /// `for (<keyword> <pattern> in <expression>) <statement>` |
| /// |
| /// Element: |
| /// `for (<keyword> <pattern> in <expression>) <body>` |
| /// |
| /// Stack effect: pushes (Expression, Pattern). |
| /// |
| /// Returns a [PatternForInResult] containing information on reported errors. |
| /// |
| /// Note, however, that the caller is responsible for reporting an error if |
| /// the static type of [expression] is potentially nullable. |
| PatternForInResult<TypeStructure, Error> analyzePatternForIn({ |
| required Node node, |
| required bool hasAwait, |
| required Pattern pattern, |
| required Expression expression, |
| required void Function() dispatchBody, |
| }) { |
| // Stack: () |
| SharedTypeSchemaView<TypeStructure> patternTypeSchema = |
| dispatchPatternSchema(pattern); |
| SharedTypeSchemaView<TypeStructure> expressionTypeSchema = hasAwait |
| ? operations.streamTypeSchema(patternTypeSchema) |
| : operations.iterableTypeSchema(patternTypeSchema); |
| SharedTypeView<TypeStructure> expressionType = |
| analyzeExpression(expression, expressionTypeSchema); |
| // Stack: (Expression) |
| |
| Error? patternForInExpressionIsNotIterableError; |
| SharedTypeView<TypeStructure>? elementType = hasAwait |
| ? operations.matchStreamType(expressionType) |
| : operations.matchIterableType(expressionType); |
| if (elementType == null) { |
| if (expressionType is SharedDynamicTypeStructure) { |
| elementType = operations.dynamicType; |
| } else if (expressionType is SharedInvalidTypeStructure) { |
| elementType = operations.errorType; |
| } else { |
| patternForInExpressionIsNotIterableError = |
| errors.patternForInExpressionIsNotIterable( |
| node: node, |
| expression: expression, |
| expressionType: expressionType, |
| ); |
| elementType = operations.errorType; |
| } |
| } |
| flow.patternForIn_afterExpression(elementType); |
| |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable>( |
| isFinal: false, |
| irrefutableContext: node, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| // Stack: (Expression, Pattern) |
| |
| flow.forEach_bodyBegin(node); |
| dispatchBody(); |
| flow.forEach_end(); |
| flow.patternForIn_end(); |
| |
| return new PatternForInResult( |
| elementType: elementType, |
| expressionType: expressionType, |
| patternForInExpressionIsNotIterableError: |
| patternForInExpressionIsNotIterableError); |
| } |
| |
| /// Analyzes a patternVariableDeclaration node of the form |
| /// `var pattern = initializer` or `final pattern = initializer`. |
| /// |
| /// [node] should be the AST node for the entire declaration, [pattern] for |
| /// the pattern, and [initializer] for the initializer. [isFinal] indicates |
| /// whether this is a final declaration. |
| /// |
| /// Returns a [PatternVariableDeclarationAnalysisResult] holding the static |
| /// type of the initializer and the type schema of the [pattern]. |
| /// |
| /// Stack effect: pushes (Expression, Pattern). |
| PatternVariableDeclarationAnalysisResult<TypeStructure> |
| analyzePatternVariableDeclaration( |
| Node node, Pattern pattern, Expression initializer, |
| {required bool isFinal}) { |
| // Stack: () |
| SharedTypeSchemaView<TypeStructure> patternSchema = |
| dispatchPatternSchema(pattern); |
| SharedTypeView<TypeStructure> initializerType = |
| analyzeExpression(initializer, patternSchema); |
| // Stack: (Expression) |
| flow.patternVariableDeclaration_afterInitializer( |
| initializer, initializerType); |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable>( |
| isFinal: isFinal, |
| irrefutableContext: node, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| _finishJoinedPatternVariables( |
| {}, componentVariables, patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.singlePattern); |
| flow.patternVariableDeclaration_end(); |
| // Stack: (Expression, Pattern) |
| return new PatternVariableDeclarationAnalysisResult( |
| initializerType: initializerType, patternSchema: patternSchema); |
| } |
| |
| /// Analyzes a record pattern. [node] is the pattern itself, and [fields] |
| /// is the list of subpatterns. |
| /// |
| /// Returns a [RecordPatternResult] with the required type and information |
| /// about reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (n * Pattern) where n = fields.length. |
| RecordPatternResult<TypeStructure, Error> analyzeRecordPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, { |
| required List<RecordPatternField<Node, Pattern>> fields, |
| }) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| List<SharedTypeView<TypeStructure>> demonstratedPositionalTypes = []; |
| List<(String, SharedTypeView<TypeStructure>)> demonstratedNamedTypes = []; |
| |
| Map<int, Error>? duplicateRecordPatternFieldErrors = |
| _reportDuplicateRecordPatternFields(node, fields); |
| |
| void dispatchField(int i, SharedTypeView<TypeStructure> matchedType) { |
| RecordPatternField<Node, Pattern> field = fields[i]; |
| flow.pushSubpattern(matchedType); |
| dispatchPattern( |
| context.withUnnecessaryWildcardKind(null), |
| field.pattern, |
| ); |
| SharedTypeView<TypeStructure> demonstratedType = |
| flow.getMatchedValueType(); |
| String? name = field.name; |
| if (name == null) { |
| demonstratedPositionalTypes.add(demonstratedType); |
| } else if (duplicateRecordPatternFieldErrors == null || |
| !duplicateRecordPatternFieldErrors.containsKey(i)) { |
| demonstratedNamedTypes.add((name, demonstratedType)); |
| } |
| flow.popSubpattern(); |
| } |
| |
| void dispatchFields(SharedTypeView<TypeStructure> matchedType) { |
| for (int i = 0; i < fields.length; i++) { |
| dispatchField(i, matchedType); |
| } |
| } |
| |
| // Build the required type. |
| int requiredTypePositionalCount = 0; |
| List<(String, SharedTypeView<TypeStructure>)> requiredTypeNamedTypes = []; |
| for (int i = 0; i < fields.length; i++) { |
| RecordPatternField<Node, Pattern> field = fields[i]; |
| String? name = field.name; |
| if (name == null) { |
| requiredTypePositionalCount++; |
| } else if (duplicateRecordPatternFieldErrors == null || |
| !duplicateRecordPatternFieldErrors.containsKey(i)) { |
| requiredTypeNamedTypes.add( |
| (name, operations.objectQuestionType), |
| ); |
| } |
| } |
| SharedTypeView<TypeStructure> requiredType = operations.recordType( |
| positional: new List.filled( |
| requiredTypePositionalCount, |
| operations.objectQuestionType, |
| ), |
| named: requiredTypeNamedTypes, |
| ); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, knownType: requiredType); |
| |
| // Stack: () |
| if (matchedValueType is SharedRecordTypeView<TypeStructure>) { |
| List<SharedTypeView<TypeStructure>>? fieldTypes = |
| _matchRecordTypeShape(fields, matchedValueType); |
| if (fieldTypes != null) { |
| assert(fieldTypes.length == fields.length); |
| for (int i = 0; i < fields.length; i++) { |
| dispatchField(i, fieldTypes[i]); |
| } |
| } else { |
| dispatchFields(operations.objectQuestionType); |
| } |
| } else if (matchedValueType is SharedDynamicTypeStructure) { |
| dispatchFields(operations.dynamicType); |
| } else if (matchedValueType is SharedInvalidTypeStructure) { |
| dispatchFields(operations.errorType); |
| } else { |
| dispatchFields(operations.objectQuestionType); |
| } |
| // Stack: (n * Pattern) where n = fields.length |
| |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && |
| !operations.isAssignableTo(matchedValueType, requiredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: requiredType, |
| ); |
| } |
| |
| SharedTypeView<TypeStructure> demonstratedType = operations.recordType( |
| positional: demonstratedPositionalTypes, named: demonstratedNamedTypes); |
| flow.promoteForPattern( |
| matchedType: matchedValueType, |
| knownType: demonstratedType, |
| matchFailsIfWrongType: false); |
| return new RecordPatternResult( |
| requiredType: requiredType, |
| duplicateRecordPatternFieldErrors: duplicateRecordPatternFieldErrors, |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a record pattern. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeRecordPatternSchema({ |
| required List<RecordPatternField<Node, Pattern>> fields, |
| }) { |
| List<SharedTypeSchemaView<TypeStructure>> positional = []; |
| List<(String, SharedTypeSchemaView<TypeStructure>)> named = []; |
| for (RecordPatternField<Node, Pattern> field in fields) { |
| SharedTypeSchemaView<TypeStructure> fieldType = |
| dispatchPatternSchema(field.pattern); |
| String? name = field.name; |
| if (name != null) { |
| named.add((name, fieldType)); |
| } else { |
| positional.add(fieldType); |
| } |
| } |
| return operations.recordTypeSchema(positional: positional, named: named); |
| } |
| |
| /// Analyzes a relational pattern. [node] is the pattern itself, and |
| /// [operand] is a constant expression that will be passed to the relational |
| /// operator. |
| /// |
| /// This method will invoke [resolveRelationalPatternOperator] to obtain |
| /// information about the operator. |
| /// |
| /// Returns a [RelationalPatternResult] with the type of the [operand] and |
| /// information about reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: pushes (Expression). |
| RelationalPatternResult<TypeStructure, Error> analyzeRelationalPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Pattern node, |
| Expression operand) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| // Stack: () |
| Error? refutablePatternInIrrefutableContextError; |
| Node? irrefutableContext = context.irrefutableContext; |
| if (irrefutableContext != null) { |
| refutablePatternInIrrefutableContextError = |
| errors.refutablePatternInIrrefutableContext( |
| pattern: node, context: irrefutableContext); |
| } |
| RelationalOperatorResolution<TypeStructure>? operator = |
| resolveRelationalPatternOperator(node, matchedValueType); |
| SharedTypeView<TypeStructure>? parameterType = operator?.parameterType; |
| bool isEquality = switch (operator?.kind) { |
| RelationalOperatorKind.equals => true, |
| RelationalOperatorKind.notEquals => true, |
| _ => false |
| }; |
| if (isEquality && parameterType != null) { |
| parameterType = operations.makeNullable(parameterType); |
| } |
| SharedTypeView<TypeStructure> operandType = analyzeExpression( |
| operand, |
| parameterType == null |
| ? operations.unknownType |
| : operations.typeToSchema(parameterType)); |
| if (isEquality) { |
| flow.equalityRelationalPattern_end(operand, operandType, |
| notEqual: operator?.kind == RelationalOperatorKind.notEquals, |
| matchedValueType: matchedValueType); |
| } else { |
| flow.nonEqualityRelationalPattern_end(); |
| } |
| // Stack: (Expression) |
| Error? argumentTypeNotAssignableError; |
| Error? operatorReturnTypeNotAssignableToBoolError; |
| if (operator != null) { |
| if (parameterType != null && |
| !operations.isAssignableTo(operandType, parameterType)) { |
| argumentTypeNotAssignableError = |
| errors.relationalPatternOperandTypeNotAssignable( |
| pattern: node, |
| operandType: operandType, |
| parameterType: operator.parameterType, |
| ); |
| } |
| if (!operations.isAssignableTo( |
| operator.returnType, operations.boolType)) { |
| operatorReturnTypeNotAssignableToBoolError = |
| errors.relationalPatternOperatorReturnTypeNotAssignableToBool( |
| pattern: node, |
| returnType: operator.returnType, |
| ); |
| } |
| } |
| return new RelationalPatternResult( |
| operandType: operandType, |
| refutablePatternInIrrefutableContextError: |
| refutablePatternInIrrefutableContextError, |
| operatorReturnTypeNotAssignableToBoolError: |
| operatorReturnTypeNotAssignableToBoolError, |
| argumentTypeNotAssignableError: argumentTypeNotAssignableError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a relational pattern. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeRelationalPatternSchema() { |
| // Relational 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 operations.unknownType; |
| } |
| |
| /// Analyzes an expression of the form `switch (expression) { cases }`. |
| /// |
| /// Returns a [SwitchExpressionResult] with the static type of the switch |
| /// expression and information about reported errors. |
| /// |
| /// Stack effect: pushes (Expression, n * ExpressionCase), where n is the |
| /// number of cases. |
| SwitchExpressionResult<TypeStructure, Error> analyzeSwitchExpression( |
| Expression node, |
| Expression scrutinee, |
| int numCases, |
| SharedTypeSchemaView<TypeStructure> schema) { |
| // Stack: () |
| |
| // The static type of a switch expression `E` of the form `switch (e0) { p1 |
| // => e1, p2 => e2, ... pn => en }` with context type `K` is computed as |
| // follows: |
| // |
| // - The scrutinee (`e0`) is first analyzed with context type `_`. |
| SharedTypeView<TypeStructure> expressionType = |
| analyzeExpression(scrutinee, operations.unknownType); |
| // Stack: (Expression) |
| handleSwitchScrutinee(expressionType); |
| flow.switchStatement_expressionEnd(null, scrutinee, expressionType); |
| |
| // - If the switch expression has no cases, its static type is `Never`. |
| Map<int, Error>? nonBooleanGuardErrors; |
| Map<int, SharedTypeView<TypeStructure>>? guardTypes; |
| SharedTypeView<TypeStructure> staticType; |
| if (numCases == 0) { |
| staticType = operations.neverType; |
| } else { |
| // - Otherwise, for each case `pi => ei`, let `Ti` be the type of `ei` |
| // inferred with context type `K`. |
| // - Let `T` be the least upper bound of the static types of all the case |
| // expressions. |
| // - Let `S` be the greatest closure of `K`. |
| SharedTypeView<TypeStructure>? t; |
| SharedTypeView<TypeStructure> s = operations.greatestClosure(schema); |
| bool allCasesSatisfyContext = true; |
| for (int i = 0; i < numCases; i++) { |
| // Stack: (Expression, i * ExpressionCase) |
| SwitchExpressionMemberInfo<Node, Expression, Variable> memberInfo = |
| getSwitchExpressionMemberInfo(node, i); |
| flow.switchStatement_beginAlternatives(); |
| flow.switchStatement_beginAlternative(); |
| handleSwitchBeforeAlternative(node, caseIndex: i, subIndex: 0); |
| Node? pattern = memberInfo.head.pattern; |
| Expression? guard; |
| if (pattern != null) { |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, |
| SharedTypeView<TypeStructure>, Variable>( |
| isFinal: false, |
| switchScrutinee: scrutinee, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| _finishJoinedPatternVariables( |
| memberInfo.head.variables, |
| componentVariables, |
| patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.singlePattern, |
| ); |
| // Stack: (Expression, i * ExpressionCase, Pattern) |
| guard = memberInfo.head.guard; |
| bool hasGuard = guard != null; |
| if (hasGuard) { |
| SharedTypeView<TypeStructure> guardType = analyzeExpression( |
| guard, operations.typeToSchema(operations.boolType)); |
| Error? nonBooleanGuardError = _checkGuardType(guard, guardType); |
| (guardTypes ??= {})[i] = guardType; |
| if (nonBooleanGuardError != null) { |
| (nonBooleanGuardErrors ??= {})[i] = nonBooleanGuardError; |
| } |
| // Stack: (Expression, i * ExpressionCase, Pattern, Expression) |
| } else { |
| handleNoGuard(node, i); |
| // Stack: (Expression, i * ExpressionCase, Pattern, Expression) |
| } |
| handleCaseHead(node, caseIndex: i, subIndex: 0); |
| } else { |
| handleDefault(node, caseIndex: i, subIndex: 0); |
| } |
| flow.switchStatement_endAlternative(guard, {}); |
| flow.switchStatement_endAlternatives(null, hasLabels: false); |
| // Stack: (Expression, i * ExpressionCase, CaseHead) |
| SharedTypeView<TypeStructure> ti = |
| analyzeExpression(memberInfo.expression, schema); |
| if (allCasesSatisfyContext && !operations.isSubtypeOf(ti, s)) { |
| allCasesSatisfyContext = false; |
| } |
| flow.switchStatement_afterCase(); |
| // Stack: (Expression, i * ExpressionCase, CaseHead, Expression) |
| if (t == null) { |
| t = ti; |
| } else { |
| t = operations.lub(t, ti); |
| } |
| finishExpressionCase(node, i); |
| // Stack: (Expression, (i + 1) * ExpressionCase) |
| } |
| // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. |
| if (!this.options.inferenceUpdate3Enabled) { |
| staticType = t!; |
| } else |
| // - If `T <: S`, then the type of `E` is `T`. |
| if (operations.isSubtypeOf(t!, s)) { |
| staticType = t; |
| } else |
| // - Otherwise, if `Ti <: S` for all `i`, then the type of `E` is `S`. |
| if (allCasesSatisfyContext) { |
| staticType = s; |
| } else |
| // - Otherwise, the type of `E` is `T`. |
| { |
| staticType = t; |
| } |
| } |
| // Stack: (Expression, numCases * ExpressionCase) |
| flow.switchStatement_end(true); |
| return new SwitchExpressionResult( |
| type: staticType, |
| nonBooleanGuardErrors: nonBooleanGuardErrors, |
| guardTypes: guardTypes); |
| } |
| |
| /// Analyzes a statement of the form `switch (expression) { cases }`. |
| /// |
| /// Stack effect: pushes (Expression, n * StatementCase), where n is the |
| /// number of cases after merging together cases that share a body. |
| SwitchStatementTypeAnalysisResult<TypeStructure, Error> |
| analyzeSwitchStatement( |
| Statement node, Expression scrutinee, final int numCases) { |
| // Stack: () |
| SharedTypeView<TypeStructure> scrutineeType = |
| analyzeExpression(scrutinee, operations.unknownType); |
| // Stack: (Expression) |
| handleSwitchScrutinee(scrutineeType); |
| flow.switchStatement_expressionEnd(node, scrutinee, scrutineeType); |
| bool hasDefault = false; |
| bool lastCaseTerminates = true; |
| Map<int, Error>? switchCaseCompletesNormallyErrors; |
| Map<int, Map<int, Error>>? nonBooleanGuardErrors; |
| Map<int, Map<int, SharedTypeView<TypeStructure>>>? guardTypes; |
| for (int caseIndex = 0; caseIndex < numCases; caseIndex++) { |
| // Stack: (Expression, numExecutionPaths * StatementCase) |
| flow.switchStatement_beginAlternatives(); |
| // Stack: (Expression, numExecutionPaths * StatementCase, |
| // numHeads * CaseHead) |
| SwitchStatementMemberInfo<Node, Statement, Expression, Variable> |
| memberInfo = getSwitchStatementMemberInfo(node, caseIndex); |
| List<CaseHeadOrDefaultInfo<Node, Expression, Variable>> heads = |
| memberInfo.heads; |
| for (int headIndex = 0; headIndex < heads.length; headIndex++) { |
| CaseHeadOrDefaultInfo<Node, Expression, Variable> head = |
| heads[headIndex]; |
| Node? pattern = head.pattern; |
| flow.switchStatement_beginAlternative(); |
| handleSwitchBeforeAlternative(node, |
| caseIndex: caseIndex, subIndex: headIndex); |
| Expression? guard; |
| if (pattern != null) { |
| Map<String, List<Variable>> componentVariables = {}; |
| Map<String, int> patternVariablePromotionKeys = {}; |
| dispatchPattern( |
| new MatchContext<Node, Expression, Pattern, |
| SharedTypeView<TypeStructure>, Variable>( |
| isFinal: false, |
| switchScrutinee: scrutinee, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ), |
| pattern, |
| ); |
| _finishJoinedPatternVariables( |
| head.variables, |
| componentVariables, |
| patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.singlePattern, |
| ); |
| // Stack: (Expression, numExecutionPaths * StatementCase, |
| // numHeads * CaseHead, Pattern), |
| guard = head.guard; |
| if (guard != null) { |
| SharedTypeView<TypeStructure> guardType = analyzeExpression( |
| guard, operations.typeToSchema(operations.boolType)); |
| Error? nonBooleanGuardError = _checkGuardType(guard, guardType); |
| ((guardTypes ??= {})[caseIndex] ??= {})[headIndex] = guardType; |
| if (nonBooleanGuardError != null) { |
| ((nonBooleanGuardErrors ??= {})[caseIndex] ??= {})[headIndex] = |
| nonBooleanGuardError; |
| } |
| // Stack: (Expression, numExecutionPaths * StatementCase, |
| // numHeads * CaseHead, Pattern, Expression), |
| } else { |
| handleNoGuard(node, caseIndex); |
| } |
| handleCaseHead(node, caseIndex: caseIndex, subIndex: headIndex); |
| } else { |
| hasDefault = true; |
| handleDefault(node, caseIndex: caseIndex, subIndex: headIndex); |
| } |
| // Stack: (Expression, numExecutionPaths * StatementCase, |
| // numHeads * CaseHead), |
| flow.switchStatement_endAlternative(guard, head.variables); |
| } |
| // Stack: (Expression, numExecutionPaths * StatementCase, |
| // numHeads * CaseHead) |
| PatternVariableInfo<Variable> patternVariableInfo = |
| flow.switchStatement_endAlternatives(node, |
| hasLabels: memberInfo.hasLabels); |
| Map<String, Variable> variables = memberInfo.variables; |
| if (memberInfo.hasLabels || heads.length > 1) { |
| _finishJoinedPatternVariables( |
| variables, |
| patternVariableInfo.componentVariables, |
| patternVariableInfo.patternVariablePromotionKeys, |
| location: JoinedPatternVariableLocation.sharedCaseScope, |
| ); |
| } |
| handleCase_afterCaseHeads(node, caseIndex, variables.values); |
| // Stack: (Expression, numExecutionPaths * StatementCase, CaseHeads) |
| // If there are joined variables, declare them. |
| for (Statement statement in memberInfo.body) { |
| dispatchStatement(statement); |
| } |
| // Stack: (Expression, numExecutionPaths * StatementCase, CaseHeads, |
| // n * Statement), where n = body.length |
| lastCaseTerminates = !flow.switchStatement_afterCase(); |
| if (caseIndex < numCases - 1 && |
| options.nullSafetyEnabled && |
| !options.patternsEnabled && |
| !lastCaseTerminates) { |
| (switchCaseCompletesNormallyErrors ??= {})[caseIndex] = errors |
| .switchCaseCompletesNormally(node: node, caseIndex: caseIndex); |
| } |
| handleMergedStatementCase(node, |
| caseIndex: caseIndex, isTerminating: lastCaseTerminates); |
| // Stack: (Expression, (numExecutionPaths + 1) * StatementCase) |
| } |
| // Stack: (Expression, numExecutionPaths * StatementCase) |
| bool isExhaustive; |
| bool requiresExhaustivenessValidation; |
| if (hasDefault) { |
| isExhaustive = true; |
| requiresExhaustivenessValidation = false; |
| } else if (options.patternsEnabled) { |
| requiresExhaustivenessValidation = |
| isExhaustive = operations.isAlwaysExhaustiveType(scrutineeType); |
| } else { |
| isExhaustive = isLegacySwitchExhaustive(node, scrutineeType); |
| requiresExhaustivenessValidation = false; |
| } |
| flow.switchStatement_end(isExhaustive); |
| return new SwitchStatementTypeAnalysisResult( |
| hasDefault: hasDefault, |
| isExhaustive: isExhaustive, |
| lastCaseTerminates: lastCaseTerminates, |
| requiresExhaustivenessValidation: requiresExhaustivenessValidation, |
| scrutineeType: scrutineeType, |
| switchCaseCompletesNormallyErrors: switchCaseCompletesNormallyErrors, |
| nonBooleanGuardErrors: nonBooleanGuardErrors, |
| guardTypes: guardTypes, |
| ); |
| } |
| |
| /// Analyzes a variable declaration of the form `type variable;` or |
| /// `var variable;`. |
| /// |
| /// [node] should be the AST node for the entire declaration, [variable] for |
| /// the variable, and [declaredType] for the type (if present). [isFinal] |
| /// indicates whether this is a final declaration. |
| /// |
| /// Stack effect: none. |
| /// |
| /// Returns the inferred type of the variable. |
| SharedTypeView<TypeStructure> analyzeUninitializedVariableDeclaration( |
| Node node, Variable variable, SharedTypeView<TypeStructure>? declaredType, |
| {required bool isFinal}) { |
| SharedTypeView<TypeStructure> inferredType = |
| declaredType ?? operations.dynamicType; |
| setVariableType(variable, inferredType); |
| flow.declare(variable, inferredType, initialized: false); |
| return inferredType; |
| } |
| |
| /// Analyzes a wildcard pattern. [node] is the pattern. |
| /// |
| /// Returns a [WildcardPatternResult] with information about reported errors. |
| /// |
| /// See [dispatchPattern] for the meaning of [context]. |
| /// |
| /// Stack effect: none. |
| WildcardPatternResult<TypeStructure, Error> analyzeWildcardPattern({ |
| required MatchContext<Node, Expression, Pattern, |
| SharedTypeView<TypeStructure>, Variable> |
| context, |
| required Pattern node, |
| required SharedTypeView<TypeStructure>? declaredType, |
| }) { |
| SharedTypeView<TypeStructure> matchedValueType = flow.getMatchedValueType(); |
| Node? irrefutableContext = context.irrefutableContext; |
| Error? patternTypeMismatchInIrrefutableContextError; |
| if (irrefutableContext != null && declaredType != null) { |
| if (!operations.isAssignableTo(matchedValueType, declaredType)) { |
| patternTypeMismatchInIrrefutableContextError = |
| errors.patternTypeMismatchInIrrefutableContext( |
| pattern: node, |
| context: irrefutableContext, |
| matchedType: matchedValueType, |
| requiredType: declaredType, |
| ); |
| } |
| } |
| |
| bool isAlwaysMatching; |
| if (declaredType != null) { |
| isAlwaysMatching = flow.promoteForPattern( |
| matchedType: matchedValueType, knownType: declaredType); |
| } else { |
| isAlwaysMatching = true; |
| } |
| |
| UnnecessaryWildcardKind? unnecessaryWildcardKind = |
| context.unnecessaryWildcardKind; |
| if (isAlwaysMatching && unnecessaryWildcardKind != null) { |
| errors.unnecessaryWildcardPattern( |
| pattern: node, |
| kind: unnecessaryWildcardKind, |
| ); |
| } |
| return new WildcardPatternResult( |
| patternTypeMismatchInIrrefutableContextError: |
| patternTypeMismatchInIrrefutableContextError, |
| matchedValueType: matchedValueType); |
| } |
| |
| /// Computes the type schema for a wildcard pattern. [declaredType] is the |
| /// explicitly declared type (if present). |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> analyzeWildcardPatternSchema({ |
| required SharedTypeView<TypeStructure>? declaredType, |
| }) { |
| return declaredType == null |
| ? operations.unknownType |
| : operations.typeToSchema(declaredType); |
| } |
| |
| /// Calls the appropriate `analyze` method according to the form of |
| /// collection [element], and then adjusts the stack as needed to combine |
| /// any sub-structures into a single collection element. |
| /// |
| /// For example, if [element] is an `if` element, calls [analyzeIfElement]. |
| /// |
| /// Stack effect: pushes (CollectionElement). |
| void dispatchCollectionElement(Node element, Object? context); |
| |
| /// Calls the appropriate `analyze` method according to the form of |
| /// [node], and then adjusts the stack as needed to combine any |
| /// sub-structures into a single expression. |
| /// |
| /// For example, if [node] is a switch expression, calls |
| /// [analyzeSwitchExpression]. |
| /// |
| /// Stack effect: pushes (Expression). |
| ExpressionTypeAnalysisResult<TypeStructure> dispatchExpression( |
| Expression node, SharedTypeSchemaView<TypeStructure> schema); |
| |
| /// Calls the appropriate `analyze` method according to the form of [pattern]. |
| /// |
| /// [context] keeps track of other contextual information pertinent to the |
| /// matching of the [pattern], such as the context of the top-level pattern, |
| /// and the information accumulated while matching previous patterns. |
| /// |
| /// Stack effect: pushes (Pattern). |
| PatternResult<TypeStructure> dispatchPattern( |
| MatchContext<Node, Expression, Pattern, SharedTypeView<TypeStructure>, |
| Variable> |
| context, |
| Node pattern); |
| |
| /// Calls the appropriate `analyze...Schema` method according to the form of |
| /// [pattern]. |
| /// |
| /// Stack effect: none. |
| SharedTypeSchemaView<TypeStructure> dispatchPatternSchema(Node pattern); |
| |
| /// Calls the appropriate `analyze` method according to the form of |
| /// [statement], and then adjusts the stack as needed to combine any |
| /// sub-structures into a single statement. |
| /// |
| /// For example, if [statement] is a switch statement, calls |
| /// [analyzeSwitchStatement]. |
| /// |
| /// Stack effect: pushes (Statement). |
| void dispatchStatement(Statement statement); |
| |
| /// Infers the type for the [pattern], should be a subtype of [matchedType]. |
| SharedTypeView<TypeStructure> downwardInferObjectPatternRequiredType({ |
| required SharedTypeView<TypeStructure> matchedType, |
| required Pattern pattern, |
| }); |
| |
| /// Called after visiting an expression case. |
| /// |
| /// [node] is the enclosing switch expression, and [caseIndex] is the index of |
| /// this code path within the switch expression's cases. |
| /// |
| /// Stack effect: pops (CaseHead, Expression) and pushes (ExpressionCase). |
| void finishExpressionCase(Expression node, int caseIndex); |
| |
| void finishJoinedPatternVariable( |
| Variable variable, { |
| required JoinedPatternVariableLocation location, |
| required JoinedPatternVariableInconsistency inconsistency, |
| required bool isFinal, |
| required SharedTypeView<TypeStructure> type, |
| }); |
| |
| /// If the [element] is a map pattern entry, returns it. |
| MapPatternEntry<Expression, Pattern>? getMapPatternEntry(Node element); |
| |
| /// If [node] is [isRestPatternElement], returns its optional pattern. |
| Pattern? getRestPatternElementPattern(Node node); |
| |
| /// Returns an [SwitchExpressionMemberInfo] object describing the [index]th |
| /// `case` or `default` clause in the switch expression [node]. |
| /// |
| /// Note: it is allowed for the client's AST nodes for `case` and `default` |
| /// clauses to implement [SwitchExpressionMemberInfo], in which case this |
| /// method can simply return the [index]th `case` or `default` clause. |
| /// |
| /// See [analyzeSwitchExpression]. |
| SwitchExpressionMemberInfo<Node, Expression, Variable> |
| getSwitchExpressionMemberInfo(Expression node, int index); |
| |
| /// Returns a [SwitchStatementMemberInfo] object describing the [caseIndex]th |
| /// `case` or `default` clause in the switch statement [node]. |
| /// |
| /// Note: it is allowed for the client's AST nodes for `case` and `default` |
| /// clauses to implement [SwitchStatementMemberInfo], in which case this |
| /// method can simply return the [caseIndex]th `case` or `default` clause. |
| /// |
| /// See [analyzeSwitchStatement]. |
| SwitchStatementMemberInfo<Node, Statement, Expression, Variable> |
| getSwitchStatementMemberInfo(Statement node, int caseIndex); |
| |
| /// Called after visiting the pattern in `if-case` statement. |
| void handle_ifCaseStatement_afterPattern({required Statement node}) {} |
| |
| /// Called after visiting the expression of an `if` element. |
| void handle_ifElement_conditionEnd(Node node) {} |
| |
| /// Called after visiting the `else` element of an `if` element. |
| void handle_ifElement_elseEnd(Node node, Node ifFalse) {} |
| |
| /// Called after visiting the `then` element of an `if` element. |
| void handle_ifElement_thenEnd(Node node, Node ifTrue) {} |
| |
| /// Called after visiting the expression of an `if` statement. |
| void handle_ifStatement_conditionEnd(Statement node) {} |
| |
| /// Called after visiting the `else` statement of an `if` statement. |
| void handle_ifStatement_elseEnd(Statement node, Statement ifFalse) {} |
| |
| /// Called after visiting the `then` statement of an `if` statement. |
| void handle_ifStatement_thenEnd(Statement node, Statement ifTrue) {} |
| |
| /// Called after visiting the left hand side of a logical-or (`||`) pattern. |
| void handle_logicalOrPattern_afterLhs(Pattern node) {} |
| |
| /// Called after visiting a merged set of `case` / `default` clauses. |
| /// |
| /// [node] is the enclosing switch statement, [caseIndex] is the index of the |
| /// merged `case` or `default` group. |
| /// |
| /// Stack effect: pops (numHeads * CaseHead) and pushes (CaseHeads). |
| void handleCase_afterCaseHeads( |
| Statement node, int caseIndex, Iterable<Variable> variables); |
| |
| /// Called after visiting a single `case` clause, consisting of a pattern and |
| /// an optional guard. |
| /// |
| /// [node] is the enclosing switch statement or switch expression, |
| /// [caseIndex] is the index of the `case` clause, and [subIndex] is the index |
| /// of the case head. |
| /// |
| /// Stack effect: pops (Pattern, Expression) and pushes (CaseHead). |
| void handleCaseHead(Node node, |
| {required int caseIndex, required int subIndex}); |
| |
| /// Called after visiting a `default` clause. |
| /// |
| /// [node] is the enclosing switch statement or switch expression and |
| /// [caseIndex] is the index of the `default` clause. |
| /// [subIndex] is the index of the case head. |
| /// |
| /// Stack effect: pushes (CaseHead). |
| void handleDefault( |
| Node node, { |
| required int caseIndex, |
| required int subIndex, |
| }); |
| |
| /// Called after visiting a rest element in a list pattern. |
| /// |
| /// Stack effect: pushes (Pattern). |
| void handleListPatternRestElement(Pattern container, Node restElement); |
| |
| /// Called after visiting an entry element in a map pattern. |
| /// |
| /// Stack effect: pushes (MapPatternElement). |
| void handleMapPatternEntry(Pattern container, Node entryElement, |
| SharedTypeView<TypeStructure> keyType); |
| |
| /// Called after visiting a rest element in a map pattern. |
| /// |
| /// Stack effect: pushes (MapPatternElement). |
| void handleMapPatternRestElement(Pattern container, Node restElement); |
| |
| /// Called after visiting a merged statement case. |
| /// |
| /// [node] is enclosing switch statement, [caseIndex] is the index of the |
| /// merged `case` or `default` group. |
| /// |
| /// If [isTerminating] is `true`, then flow analysis has determined that the |
| /// case ends in a construct that doesn't complete normally (e.g. a `break`, |
| /// `return`, `continue`, `throw`, or infinite loop); the client can use this |
| /// to determine whether a jump is needed to the end of the switch statement. |
| /// |
| /// Stack effect: pops (CaseHeads, numStatements * Statement) and pushes |
| /// (StatementCase). |
| void handleMergedStatementCase(Statement node, |
| {required int caseIndex, required bool isTerminating}); |
| |
| /// Called when visiting a syntactic construct where there is an implicit |
| /// no-op collection element. For example, this is called in place of the |
| /// missing `else` part of an `if` element that lacks an `else` clause. |
| /// |
| /// Stack effect: pushes (CollectionElement). |
| void handleNoCollectionElement(Node node); |
| |
| /// Called when visiting a `case` that lacks a guard clause. Since the lack |
| /// of a guard clause is semantically equivalent to `when true`, this method |
| /// should behave similarly to visiting the boolean literal `true`. |
| /// |
| /// [node] is the enclosing switch statement, switch expression, or `if`, and |
| /// [caseIndex] is the index of the `case` within [node]. |
| /// |
| /// Stack effect: pushes (Expression). |
| void handleNoGuard(Node node, int caseIndex); |
| |
| /// Called when visiting a syntactic construct where there is an implicit |
| /// no-op statement. For example, this is called in place of the missing |
| /// `else` part of an `if` statement that lacks an `else` clause. |
| /// |
| /// Stack effect: pushes (Statement). |
| void handleNoStatement(Statement node); |
| |
| /// Called before visiting a single `case` or `default` clause. |
| /// |
| /// [node] is the enclosing switch statement or switch expression and |
| /// [caseIndex] is the index of the `case` or `default` clause. |
| /// [subIndex] is the index of the case head. |
| void handleSwitchBeforeAlternative(Node node, |
| {required int caseIndex, required int subIndex}); |
| |
| /// Called after visiting the scrutinee part of a switch statement or switch |
| /// expression. This is a hook to allow the client to start exhaustiveness |
| /// analysis. |
| /// |
| /// [type] is the static type of the scrutinee expression. |
| /// |
| /// TODO(paulberry): move exhaustiveness analysis into the shared code and |
| /// eliminate this method. |
| /// |
| /// Stack effect: none. |
| void handleSwitchScrutinee(SharedTypeView<TypeStructure> type); |
| |
| /// Queries whether the switch statement or expression represented by [node] |
| /// was exhaustive. [expressionType] is the static type of the scrutinee. |
| /// |
| /// Will only be called if the switch statement or expression lacks a |
| /// `default` clause, and patterns support is disabled. |
| bool isLegacySwitchExhaustive( |
| Node node, SharedTypeView<TypeStructure> expressionType); |
| |
| /// Returns whether [node] is a rest element in a list or map pattern. |
| bool isRestPatternElement(Node node); |
| |
| /// Queries whether [pattern] is a variable pattern. |
| bool isVariablePattern(Node pattern); |
| |
| /// Returns the type of the property in [receiverType] that corresponds to |
| /// the name of the [field]. If the property cannot be resolved, the client |
| /// should report an error, and return `dynamic` for recovery. |
| (Object?, SharedTypeView<TypeStructure>) resolveObjectPatternPropertyGet({ |
| required Pattern objectPattern, |
| required SharedTypeView<TypeStructure> receiverType, |
| required RecordPatternField<Node, Pattern> field, |
| }); |
| |
| /// Resolves the relational operator for [node] assuming that the value being |
| /// matched has static type [matchedValueType]. |
| /// |
| /// If no operator is found, `null` should be returned. (This could happen |
| /// either because the code is invalid, or because [matchedValueType] is |
| /// `dynamic`). |
| RelationalOperatorResolution<TypeStructure>? resolveRelationalPatternOperator( |
| Pattern node, SharedTypeView<TypeStructure> matchedValueType); |
| |
| /// Records that type inference has assigned a [type] to a [variable]. This |
| /// is called once per variable, regardless of whether the variable's type is |
| /// explicit or inferred. |
| void setVariableType(Variable variable, SharedTypeView<TypeStructure> type); |
| |
| /// Computes the type that should be inferred for an implicitly typed variable |
| /// whose initializer expression has static type [type]. |
| SharedTypeView<TypeStructure> variableTypeFromInitializerType( |
| SharedTypeView<TypeStructure> type); |
| |
| /// Common functionality shared by [analyzeIfStatement] and |
| /// [analyzeIfCaseStatement]. |
| /// |
| /// Stack effect: pushes (Statement ifTrue, Statement ifFalse). |
| void _analyzeIfCommon(Statement node, Statement ifTrue, Statement? ifFalse) { |
| // Stack: () |
| dispatchStatement(ifTrue); |
| handle_ifStatement_thenEnd(node, ifTrue); |
| // Stack: (Statement ifTrue) |
| if (ifFalse == null) { |
| handleNoStatement(node); |
| flow.ifStatement_end(false); |
| } else { |
| flow.ifStatement_elseBegin(); |
| dispatchStatement(ifFalse); |
| flow.ifStatement_end(true); |
| handle_ifStatement_elseEnd(node, ifFalse); |
| } |
| // Stack: (Statement ifTrue, Statement ifFalse) |
| } |
| |
| /// Common functionality shared by [analyzeIfElement] and |
| /// [analyzeIfCaseElement]. |
| /// |
| /// Stack effect: pushes (CollectionElement ifTrue, |
| /// CollectionElement ifFalse). |
| void _analyzeIfElementCommon( |
| Node node, Node ifTrue, Node? ifFalse, Object? context) { |
| // Stack: () |
| dispatchCollectionElement(ifTrue, context); |
| handle_ifElement_thenEnd(node, ifTrue); |
| // Stack: (CollectionElement ifTrue) |
| if (ifFalse == null) { |
| handleNoCollectionElement(node); |
| flow.ifStatement_end(false); |
| } else { |
| flow.ifStatement_elseBegin(); |
| dispatchCollectionElement(ifFalse, context); |
| flow.ifStatement_end(true); |
| handle_ifElement_elseEnd(node, ifFalse); |
| } |
| // Stack: (CollectionElement ifTrue, CollectionElement ifFalse) |
| } |
| |
| Error? _checkGuardType( |
| Expression expression, SharedTypeView<TypeStructure> type) { |
| // TODO(paulberry): harmonize this with analyzer's checkForNonBoolExpression |
| // 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 (!operations.isAssignableTo(type, operations.boolType)) { |
| return errors.nonBooleanCondition(node: expression); |
| } |
| return null; |
| } |
| |
| void _finishJoinedPatternVariables( |
| Map<String, Variable> variables, |
| Map<String, List<Variable>> componentVariables, |
| Map<String, int> patternVariablePromotionKeys, { |
| required JoinedPatternVariableLocation location, |
| }) { |
| assert(() { |
| // Every entry in `variables` should match a variable we know about. |
| for (String variableName in variables.keys) { |
| assert(patternVariablePromotionKeys.containsKey(variableName)); |
| } |
| return true; |
| }()); |
| for (MapEntry<String, int> entry in patternVariablePromotionKeys.entries) { |
| String variableName = entry.key; |
| int promotionKey = entry.value; |
| Variable? variable = variables[variableName]; |
| List<Variable> components = componentVariables[variableName] ?? []; |
| bool isFirst = true; |
| SharedTypeView<TypeStructure>? typeIfConsistent; |
| bool? isFinalIfConsistent; |
| bool isIdenticalToComponent = false; |
| for (Variable component in components) { |
| if (identical(variable, component)) { |
| isIdenticalToComponent = true; |
| } |
| SharedTypeView<TypeStructure> componentType = |
| operations.variableType(component); |
| bool isComponentFinal = operations.isVariableFinal(component); |
| if (isFirst) { |
| typeIfConsistent = componentType; |
| isFinalIfConsistent = isComponentFinal; |
| isFirst = false; |
| } else { |
| bool inconsistencyFound = false; |
| if (typeIfConsistent != null && |
| !_structurallyEqualAfterNormTypes( |
| typeIfConsistent, componentType)) { |
| typeIfConsistent = null; |
| inconsistencyFound = true; |
| } |
| if (isFinalIfConsistent != null && |
| isFinalIfConsistent != isComponentFinal) { |
| isFinalIfConsistent = null; |
| inconsistencyFound = true; |
| } |
| if (inconsistencyFound && |
| location == JoinedPatternVariableLocation.singlePattern && |
| variable != null) { |
| errors.inconsistentJoinedPatternVariable( |
| variable: variable, component: component); |
| } |
| } |
| } |
| if (variable != null) { |
| if (!isIdenticalToComponent) { |
| finishJoinedPatternVariable(variable, |
| location: location, |
| inconsistency: typeIfConsistent != null && |
| isFinalIfConsistent != null |
| ? JoinedPatternVariableInconsistency.none |
| : JoinedPatternVariableInconsistency.differentFinalityOrType, |
| isFinal: isFinalIfConsistent ?? false, |
| type: typeIfConsistent ?? operations.errorType); |
| flow.assignMatchedPatternVariable(variable, promotionKey); |
| } |
| } |
| } |
| } |
| |
| /// If the shape described by [fields] is the same as the shape of the |
| /// [matchedType], returns matched types for each field in [fields]. |
| /// Otherwise returns `null`. |
| List<SharedTypeView<TypeStructure>>? _matchRecordTypeShape( |
| List<RecordPatternField<Node, Pattern>> fields, |
| SharedRecordTypeView<TypeStructure> matchedType, |
| ) { |
| Map<String, SharedTypeView<TypeStructure>> matchedTypeNamed = {}; |
| for (var SharedNamedTypeView<TypeStructure>(:name, :type) |
| in matchedType.namedTypes) { |
| matchedTypeNamed[name] = type; |
| } |
| |
| List<SharedTypeView<TypeStructure>> result = []; |
| int namedCount = 0; |
| Iterator<SharedTypeView<TypeStructure>> positionalIterator = |
| matchedType.positionalTypes.iterator; |
| for (RecordPatternField<Node, Pattern> field in fields) { |
| SharedTypeView<TypeStructure>? fieldType; |
| String? name = field.name; |
| if (name != null) { |
| fieldType = matchedTypeNamed[name]; |
| if (fieldType == null) { |
| return null; |
| } |
| namedCount++; |
| } else { |
| if (!positionalIterator.moveNext()) { |
| return null; |
| } |
| fieldType = positionalIterator.current; |
| } |
| result.add(fieldType); |
| } |
| if (positionalIterator.moveNext()) { |
| return null; |
| } |
| if (namedCount != matchedTypeNamed.length) { |
| return null; |
| } |
| |
| assert(result.length == fields.length); |
| return result; |
| } |
| |
| /// Reports errors for duplicate named record fields. |
| Map<int, Error>? _reportDuplicateRecordPatternFields( |
| Pattern pattern, List<RecordPatternField<Node, Pattern>> fields) { |
| Map<int, Error>? errorResults; |
| Map<String, RecordPatternField<Node, Pattern>> nameToField = {}; |
| for (int i = 0; i < fields.length; i++) { |
| RecordPatternField<Node, Pattern> field = fields[i]; |
| String? name = field.name; |
| if (name != null) { |
| RecordPatternField<Node, Pattern>? original = nameToField[name]; |
| if (original != null) { |
| (errorResults ??= {})[i] = errors.duplicateRecordPatternField( |
| objectOrRecordPattern: pattern, |
| name: name, |
| original: original, |
| duplicate: field, |
| ); |
| } else { |
| nameToField[name] = field; |
| } |
| } |
| } |
| return errorResults; |
| } |
| |
| bool _structurallyEqualAfterNormTypes(SharedTypeView<TypeStructure> type1, |
| SharedTypeView<TypeStructure> type2) { |
| SharedTypeView<TypeStructure> norm1 = operations.normalize(type1); |
| SharedTypeView<TypeStructure> norm2 = operations.normalize(type2); |
| return norm1.unwrapTypeView().isStructurallyEqualTo(norm2.unwrapTypeView()); |
| } |
| } |
| |
| /// Interface used by the shared [TypeAnalyzer] logic to report error conditions |
| /// up to the client during the "visit" phase of type analysis. |
| abstract class TypeAnalyzerErrors< |
| Node extends Object, |
| Statement extends Node, |
| Expression extends Node, |
| Variable extends Object, |
| Type extends Object, |
| Pattern extends Node, |
| Error> implements TypeAnalyzerErrorsBase { |
| /// Called if pattern support is disabled and a case constant's static type |
| /// doesn't properly match the scrutinee's static type. |
| Error caseExpressionTypeMismatch( |
| {required Expression scrutinee, |
| required Expression caseExpression, |
| required Type scrutineeType, |
| required Type caseExpressionType, |
| required bool nullSafetyEnabled}); |
| |
| /// Called for variable that is assigned more than once. |
| /// |
| /// Returns an error object that is passed on the the caller. |
| Error duplicateAssignmentPatternVariable({ |
| required Variable variable, |
| required Pattern original, |
| required Pattern duplicate, |
| }); |
| |
| /// Called for a pair of named fields have the same name. |
| Error duplicateRecordPatternField({ |
| required Pattern objectOrRecordPattern, |
| required String name, |
| required RecordPatternField<Node, Pattern> original, |
| required RecordPatternField<Node, Pattern> duplicate, |
| }); |
| |
| /// Called for a duplicate rest pattern found in a list or map pattern. |
| Error duplicateRestPattern({ |
| required Pattern mapOrListPattern, |
| required Node original, |
| required Node duplicate, |
| }); |
| |
| /// Called if a map pattern does not have elements. |
| /// |
| /// [pattern] is the map pattern. |
| Error emptyMapPattern({ |
| required Pattern pattern, |
| }); |
| |
| /// Called when both branches have variables with the same name, but these |
| /// variables either don't have the same finality, or their `NORM` types |
| /// are not structurally equal. |
| void inconsistentJoinedPatternVariable({ |
| required Variable variable, |
| required Variable component, |
| }); |
| |
| /// Called when a null-assert or null-check pattern is used with the matched |
| /// type that is strictly non-nullable, so the null check is not necessary. |
| Error? matchedTypeIsStrictlyNonNullable({ |
| required Pattern pattern, |
| required Type matchedType, |
| }); |
| |
| /// Called when the matched type of a cast pattern is a subtype of the |
| /// required type, so the cast is not necessary. |
| void matchedTypeIsSubtypeOfRequired({ |
| required Pattern pattern, |
| required Type matchedType, |
| required Type requiredType, |
| }); |
| |
| /// Called if the static type of a condition is not assignable to `bool`. |
| Error nonBooleanCondition({required Expression node}); |
| |
| /// Called if in a pattern `for-in` statement or element, the [expression] |
| /// that should be an `Iterable` (or dynamic) is actually not. |
| /// |
| /// [expressionType] is the actual type of the [expression]. |
| Error patternForInExpressionIsNotIterable({ |
| required Node node, |
| required Expression expression, |
| required Type expressionType, |
| }); |
| |
| /// Called if, for a pattern in an irrefutable context, the matched type of |
| /// the pattern is not assignable to the required type. |
| /// |
| /// [pattern] is the AST node of the pattern with the type error, [context] is |
| /// the containing AST node that established an irrefutable context, |
| /// [matchedType] is the matched type, and [requiredType] is the required |
| /// type. |
| Error patternTypeMismatchInIrrefutableContext( |
| {required Pattern pattern, |
| required Node context, |
| required Type matchedType, |
| required Type requiredType}); |
| |
| /// Called if a refutable pattern is illegally used in an irrefutable context. |
| /// |
| /// [pattern] is the AST node of the refutable pattern, and [context] is the |
| /// containing AST node that established an irrefutable context. |
| /// |
| /// TODO(paulberry): move this error reporting to the parser. |
| Error refutablePatternInIrrefutableContext( |
| {required Node pattern, required Node context}); |
| |
| /// Called if the operand of the [pattern] has the type [operandType], which |
| /// is not assignable to [parameterType] of the invoked relational operator. |
| Error relationalPatternOperandTypeNotAssignable({ |
| required Pattern pattern, |
| required Type operandType, |
| required Type parameterType, |
| }); |
| |
| /// Called if the [returnType] of the invoked relational operator is not |
| /// assignable to `bool`. |
| Error relationalPatternOperatorReturnTypeNotAssignableToBool({ |
| required Pattern pattern, |
| required Type returnType, |
| }); |
| |
| /// Called if a rest pattern found inside a map pattern. |
| /// |
| /// [node] is the map pattern. [element] is the rest pattern. |
| Error restPatternInMap({required Pattern node, required Node element}); |
| |
| /// Called if one of the case bodies of a switch statement completes normally |
| /// (other than the last case body), and the "patterns" feature is not |
| /// enabled. |
| /// |
| /// [node] is the AST node of the switch statement. [caseIndex] is the index |
| /// of the merged case with the erroneous case body. |
| Error switchCaseCompletesNormally( |
| {required Statement node, required int caseIndex}); |
| |
| /// Called when a wildcard pattern appears in the context where it is not |
| /// necessary, e.g. `0 && var _` vs. `[var _]`, and does not add anything |
| /// to type promotion, e.g. `final x = 0; if (x case int _ && > 0) {}`. |
| void unnecessaryWildcardPattern({ |
| required Pattern pattern, |
| required UnnecessaryWildcardKind kind, |
| }); |
| } |
| |
| /// Base class for error reporting callbacks that might be reported either in |
| /// the "pre-visit" or the "visit" phase of type analysis. |
| abstract class TypeAnalyzerErrorsBase { |
| /// Called when the [TypeAnalyzer] encounters a condition which should be |
| /// impossible if the user's code is free from static errors, but which might |
| /// arise as a result of error recovery. To verify this invariant, the client |
| /// should double check (preferably using an assertion) that at least one |
| /// error is reported. |
| /// |
| /// Note that the error might be reported after this method is called. |
| void assertInErrorRecovery(); |
| } |
| |
| /// Options affecting the behavior of [TypeAnalyzer]. |
| /// |
| /// The client is free to `implement` or `extend` this class. |
| class TypeAnalyzerOptions { |
| final bool nullSafetyEnabled; |
| |
| final bool patternsEnabled; |
| |
| final bool inferenceUpdate3Enabled; |
| |
| TypeAnalyzerOptions( |
| {required this.nullSafetyEnabled, |
| required this.patternsEnabled, |
| required this.inferenceUpdate3Enabled}); |
| } |