| // 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 'type_analyzer.dart'; |
| |
| /// Container for the result of running type analysis on an expression. |
| /// |
| /// This class keeps track of a provisional type of the expression (prior to |
| /// resolving null shorting) as well as the information necessary to resolve |
| /// null shorting. |
| abstract class ExpressionTypeAnalysisResult<Type extends Object> { |
| /// Type of the expression before resolving null shorting. |
| /// |
| /// For example, if `this` is the result of analyzing `(... as int?)?.isEven`, |
| /// [provisionalType] will be `bool`, because the `isEven` getter returns |
| /// `bool`, and it is not yet known (until looking at the surrounding code) |
| /// whether there will be additional selectors after `isEven` that should act |
| /// on the `bool` type. |
| Type get provisionalType; |
| |
| /// Resolves any pending null shorting. For example, if `this` is the result |
| /// of analyzing `(... as int?)?.isEven`, then calling [resolveShorting] will |
| /// cause the `?.` to be desugared (if code generation is occurring) and will |
| /// return the type `bool?`. |
| /// |
| /// TODO(paulberry): document what calls back to the client might be made by |
| /// invoking this method. |
| Type resolveShorting(); |
| } |
| |
| /// Container for the result of running type analysis on an integer literal. |
| class IntTypeAnalysisResult<Type extends Object> |
| extends SimpleTypeAnalysisResult<Type> { |
| /// Whether the integer literal was converted to a double. |
| final bool convertedToDouble; |
| |
| IntTypeAnalysisResult({required super.type, required this.convertedToDouble}); |
| } |
| |
| /// Information about the code context surrounding a pattern match. |
| class MatchContext<Node extends Object, Expression extends Node, |
| Pattern extends Node, Type extends Object, Variable extends Object> { |
| /// If non-`null`, the match is being done in an irrefutable context, and this |
| /// is the surrounding AST node that establishes the irrefutable context. |
| final Node? irrefutableContext; |
| |
| /// Indicates whether variables declared in the pattern should be `final`. |
| final bool isFinal; |
| |
| /// Indicates whether variables declared in the pattern should be `late`. |
| final bool isLate; |
| |
| /// The switch scrutinee, or `null` if this pattern does not occur in a switch |
| /// statement or switch expression, or this pattern is not the top-level |
| /// pattern. |
| final Expression? switchScrutinee; |
| |
| /// If the match is being done in a pattern assignment, the set of variables |
| /// assigned so far. |
| final Map<Variable, Pattern>? assignedVariables; |
| |
| /// For each variable name in the pattern, a list of the variables which might |
| /// capture that variable's value, depending upon which alternative is taken |
| /// in a logical-or pattern. |
| final Map<String, List<Variable>> componentVariables; |
| |
| /// For each variable name in the pattern, the promotion key holding the value |
| /// captured by that variable. |
| final Map<String, int> patternVariablePromotionKeys; |
| |
| /// If non-null, the warning that should be issued if the pattern is `_` |
| final UnnecessaryWildcardKind? unnecessaryWildcardKind; |
| |
| MatchContext({ |
| this.irrefutableContext, |
| required this.isFinal, |
| this.isLate = false, |
| this.switchScrutinee, |
| this.assignedVariables, |
| required this.componentVariables, |
| required this.patternVariablePromotionKeys, |
| this.unnecessaryWildcardKind, |
| }); |
| |
| /// Returns a modified version of `this`, with [irrefutableContext] set to |
| /// `null`. This is used to suppress cascading errors after reporting |
| /// [TypeAnalyzerErrors.refutablePatternInIrrefutableContext]. |
| MatchContext<Node, Expression, Pattern, Type, Variable> makeRefutable() => |
| irrefutableContext == null |
| ? this |
| : new MatchContext( |
| isFinal: isFinal, |
| isLate: isLate, |
| switchScrutinee: switchScrutinee, |
| assignedVariables: assignedVariables, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| ); |
| |
| /// Returns a modified version of `this`, with a new value of |
| /// [patternVariablePromotionKeys]. |
| MatchContext<Node, Expression, Pattern, Type, Variable> withPromotionKeys( |
| Map<String, int> patternVariablePromotionKeys) => |
| new MatchContext( |
| irrefutableContext: irrefutableContext, |
| isFinal: isFinal, |
| isLate: isLate, |
| switchScrutinee: null, |
| assignedVariables: assignedVariables, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| unnecessaryWildcardKind: unnecessaryWildcardKind, |
| ); |
| |
| /// Returns a modified version of `this`, with both [initializer] and |
| /// [switchScrutinee] set to `null` (because this context is not for a |
| /// top-level pattern anymore). |
| MatchContext<Node, Expression, Pattern, Type, Variable> |
| withUnnecessaryWildcardKind( |
| UnnecessaryWildcardKind? unnecessaryWildcardKind) { |
| return new MatchContext( |
| irrefutableContext: irrefutableContext, |
| isFinal: isFinal, |
| isLate: isLate, |
| assignedVariables: assignedVariables, |
| switchScrutinee: null, |
| componentVariables: componentVariables, |
| patternVariablePromotionKeys: patternVariablePromotionKeys, |
| unnecessaryWildcardKind: unnecessaryWildcardKind, |
| ); |
| } |
| } |
| |
| /// Container for the result of running type analysis on a pattern assignment. |
| class PatternAssignmentAnalysisResult<Type extends Object> |
| extends SimpleTypeAnalysisResult<Type> { |
| /// The type schema of the pattern on the left hand size of the assignment. |
| final Type patternSchema; |
| |
| PatternAssignmentAnalysisResult({ |
| required this.patternSchema, |
| required super.type, |
| }); |
| } |
| |
| /// Container for the result of running type analysis on an expression that does |
| /// not contain any null shorting. |
| class SimpleTypeAnalysisResult<Type extends Object> |
| implements ExpressionTypeAnalysisResult<Type> { |
| /// The static type of the expression. |
| final Type type; |
| |
| SimpleTypeAnalysisResult({required this.type}); |
| |
| @override |
| Type get provisionalType => type; |
| |
| @override |
| Type resolveShorting() => type; |
| } |
| |
| /// Result for analyzing a switch expression in |
| /// [TypeAnalyzer.analyzeSwitchExpression]. |
| class SwitchExpressionResult<Type extends Object, Error> |
| extends SimpleTypeAnalysisResult<Type> { |
| /// Errors for non-bool guards. |
| /// |
| /// The key is the case index of the erroneous guard. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Error>? nonBooleanGuardErrors; |
| |
| /// The types of the guard expressions. |
| /// |
| /// The key is the case index of the guard. |
| /// |
| /// This is `null` if no such guards where present. |
| final Map<int, Type>? guardTypes; |
| |
| SwitchExpressionResult( |
| {required super.type, |
| required this.nonBooleanGuardErrors, |
| required this.guardTypes}); |
| } |
| |
| /// Container for the result of running type analysis on an integer literal. |
| class SwitchStatementTypeAnalysisResult<Type extends Object, Error> { |
| /// Whether the switch statement had a `default` clause. |
| final bool hasDefault; |
| |
| /// Whether the switch statement was exhaustive. |
| final bool isExhaustive; |
| |
| /// Whether the last case body in the switch statement terminated. |
| final bool lastCaseTerminates; |
| |
| /// If `true`, patterns support is enabled, there is no default clause, and |
| /// the static type of the scrutinee expression is an "always exhaustive" |
| /// type. Therefore, flow analysis has assumed (without checking) that the |
| /// switch statement is exhaustive. So at a later stage of compilation, the |
| /// exhaustiveness checking algorithm should check whether this switch |
| /// statement was exhaustive, and report a compile-time error if it wasn't. |
| final bool requiresExhaustivenessValidation; |
| |
| /// The static type of the scrutinee expression. |
| final Type scrutineeType; |
| |
| /// Errors for the cases that don't complete normally. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Error>? switchCaseCompletesNormallyErrors; |
| |
| /// Errors for non-bool guards. |
| /// |
| /// The keys of the maps are case and head indices of the erroneous guard. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Map<int, Error>>? nonBooleanGuardErrors; |
| |
| /// The types of the guard expressions. |
| /// |
| /// The keys of the maps are case and head indices of the guard. |
| /// |
| /// This is `null` if no such guards where present. |
| final Map<int, Map<int, Type>>? guardTypes; |
| |
| SwitchStatementTypeAnalysisResult({ |
| required this.hasDefault, |
| required this.isExhaustive, |
| required this.lastCaseTerminates, |
| required this.requiresExhaustivenessValidation, |
| required this.scrutineeType, |
| required this.switchCaseCompletesNormallyErrors, |
| required this.nonBooleanGuardErrors, |
| required this.guardTypes, |
| }); |
| } |
| |
| /// The location of a wildcard pattern that was found unnecessary. |
| /// |
| /// When a wildcard pattern always matches, and is not required by the |
| /// by the location, we can report it as unnecessary. The locations where it |
| /// is necessary include list patterns, record patterns, cast patterns, etc. |
| enum UnnecessaryWildcardKind { |
| /// The wildcard pattern is the left or the right side of a logical-and |
| /// pattern. Because we found that is always matches, it has no effect, |
| /// and can be removed. |
| logicalAndPatternOperand, |
| } |
| |
| /// Result for analyzing an assigned variable pattern in |
| /// [TypeAnalyzer.analyzeAssignedVariablePattern]. |
| class AssignedVariablePatternResult<Error> { |
| /// Error for when a variable was assigned multiple times within a pattern. |
| final Error? duplicateAssignmentPatternVariableError; |
| |
| /// Error for when the matched value type is not assignable to the variable |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| AssignedVariablePatternResult( |
| {required this.duplicateAssignmentPatternVariableError, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing an object pattern in |
| /// [TypeAnalyzer.analyzeObjectPattern]. |
| class ObjectPatternResult<Type extends Object, Error> { |
| /// The required type of the object pattern. |
| final Type requiredType; |
| |
| /// Errors for when the same property name was used multiple times in the |
| /// object pattern. |
| /// |
| /// The key is the index of the duplicate field within the object pattern. |
| /// |
| /// This is `null` if no such properties were found. |
| final Map<int, Error>? duplicateRecordPatternFieldErrors; |
| |
| /// Error for when the matched value type is not assignable to the required |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| ObjectPatternResult( |
| {required this.requiredType, |
| required this.duplicateRecordPatternFieldErrors, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a record pattern in |
| /// [TypeAnalyzer.analyzeRecordPattern]. |
| class RecordPatternResult<Type extends Object, Error> { |
| /// The required type of the record pattern. |
| final Type requiredType; |
| |
| /// Errors for when the same property name was used multiple times in the |
| /// record pattern. |
| /// |
| /// The key is the index of the duplicate field within the record pattern. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Error>? duplicateRecordPatternFieldErrors; |
| |
| /// Error for when the matched value type is not assignable to the required |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| RecordPatternResult( |
| {required this.requiredType, |
| required this.duplicateRecordPatternFieldErrors, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a list pattern in [TypeAnalyzer.analyzeListPattern]. |
| class ListPatternResult<Type extends Object, Error> { |
| /// The required type of the list pattern. |
| final Type requiredType; |
| |
| /// Errors for when multiple rest patterns occurred within the list pattern. |
| /// |
| /// The key is the index of the pattern within the list pattern. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Error>? duplicateRestPatternErrors; |
| |
| /// Error for when the matched value type is not assignable to the required |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| ListPatternResult( |
| {required this.requiredType, |
| required this.duplicateRestPatternErrors, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a map pattern in [TypeAnalyzer.analyzeMapPattern]. |
| class MapPatternResult<Type extends Object, Error> { |
| /// The required type of the map pattern. |
| final Type requiredType; |
| |
| /// Errors for when multiple rest patterns occurred within the map pattern. |
| /// |
| /// The key is the index of the pattern within the map pattern. |
| /// |
| /// This is `null` if no such errors where found. |
| final Map<int, Error>? duplicateRestPatternErrors; |
| |
| /// Error for when the matched value type is not assignable to the required |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| MapPatternResult( |
| {required this.requiredType, |
| required this.duplicateRestPatternErrors, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a constant pattern in |
| /// [TypeAnalyzer.analyzeConstantPattern]. |
| class ConstantPatternResult<Type extends Object, Error> { |
| /// The static type of the constant expression. |
| final Type expressionType; |
| |
| /// Error for when the pattern occurred in an irrefutable context. |
| final Error? refutablePatternInIrrefutableContextError; |
| |
| /// Error for when the pattern, used as a case constant expression, does not |
| /// have a valid type wrt. the switch expression type. |
| final Error? caseExpressionTypeMismatchError; |
| |
| ConstantPatternResult( |
| {required this.expressionType, |
| required this.refutablePatternInIrrefutableContextError, |
| required this.caseExpressionTypeMismatchError}); |
| } |
| |
| /// Result for analyzing a declared variable pattern in |
| /// [TypeAnalyzer.analyzeDeclaredVariablePattern]. |
| class DeclaredVariablePatternResult<Type extends Object, Error> { |
| /// The static type of the variable. |
| final Type staticType; |
| |
| /// Error for when the matched value type is not assignable to the static |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| DeclaredVariablePatternResult( |
| {required this.staticType, |
| required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a logical or pattern in |
| /// [TypeAnalyzer.analyzeLogicalOrPattern]. |
| class LogicalOrPatternResult<Error> { |
| /// Error for when the pattern occurred in an irrefutable context. |
| final Error? refutablePatternInIrrefutableContextError; |
| |
| LogicalOrPatternResult( |
| {required this.refutablePatternInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing a relational pattern in |
| /// [TypeAnalyzer.analyzeRelationalPattern]. |
| class RelationalPatternResult<Type extends Object, Error> { |
| /// The static type of the operand. |
| final Type operandType; |
| |
| /// Error for when the pattern occurred in an irrefutable context. |
| final Error? refutablePatternInIrrefutableContextError; |
| |
| /// Error for when the operand type is not assignable to the parameter type |
| /// of the relational operator. |
| final Error? argumentTypeNotAssignableError; |
| |
| /// Error for when the relational operator does not return a bool. |
| final Error? operatorReturnTypeNotAssignableToBoolError; |
| |
| RelationalPatternResult( |
| {required this.operandType, |
| required this.refutablePatternInIrrefutableContextError, |
| required this.argumentTypeNotAssignableError, |
| required this.operatorReturnTypeNotAssignableToBoolError}); |
| } |
| |
| /// Result for analyzing a null check or null assert pattern in |
| /// [TypeAnalyzer.analyzeNullCheckOrAssertPattern]. |
| class NullCheckOrAssertPatternResult<Error> { |
| /// Error for when the pattern occurred in an irrefutable context. |
| final Error? refutablePatternInIrrefutableContextError; |
| |
| /// Error for when the matched type is known to be non-null. |
| final Error? matchedTypeIsStrictlyNonNullableError; |
| |
| NullCheckOrAssertPatternResult( |
| {required this.refutablePatternInIrrefutableContextError, |
| required this.matchedTypeIsStrictlyNonNullableError}); |
| } |
| |
| /// Result for analyzing a wildcard pattern |
| /// [TypeAnalyzer.analyzeWildcardPattern]. |
| class WildcardPatternResult<Error> { |
| /// Error for when the matched value type is not assignable to the wildcard |
| /// type in an irrefutable context. |
| final Error? patternTypeMismatchInIrrefutableContextError; |
| |
| WildcardPatternResult( |
| {required this.patternTypeMismatchInIrrefutableContextError}); |
| } |
| |
| /// Result for analyzing an if-case statement or element in |
| /// [TypeAnalyzer.analyzeIfCaseStatement] and |
| /// [TypeAnalyzer.analyzeIfCaseElement]. |
| class IfCaseStatementResult<Type extends Object, Error> { |
| /// The static type of the matched expression. |
| final Type matchedExpressionType; |
| |
| /// Error for when the guard has a non-bool type. |
| final Error? nonBooleanGuardError; |
| |
| /// The type of the guard expression, if present. |
| final Type? guardType; |
| |
| IfCaseStatementResult( |
| {required this.matchedExpressionType, |
| required this.nonBooleanGuardError, |
| required this.guardType}); |
| } |
| |
| /// Result for analyzing a pattern-for-in statement or element in |
| /// [TypeAnalyzer.analyzePatternForIn]. |
| class PatternForInResult<Error> { |
| /// Error for when the expression is not an iterable. |
| final Error? patternForInExpressionIsNotIterableError; |
| |
| PatternForInResult({required this.patternForInExpressionIsNotIterableError}); |
| } |