Index: pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart |
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7adeedeafb8a25012fd30ea2a0003f44ac2612d5 |
--- /dev/null |
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart |
@@ -0,0 +1,575 @@ |
+// Copyright (c) 2017, 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.md file. |
+ |
+import 'package:front_end/src/fasta/errors.dart'; |
+import 'package:front_end/src/fasta/type_inference/type_inferrer.dart'; |
+import 'package:kernel/ast.dart'; |
+ |
+/// Keeps track of the state necessary to perform type promotion. |
+/// |
+/// Theory of operation: during parsing, the BodyBuilder calls methods in this |
+/// class to inform it of syntactic constructs that are encountered. Those |
+/// methods maintain a linked list of [TypePromotionFact] objects tracking what |
+/// is known about the state of each variable at the current point in the code, |
+/// as well as a linked list of [TypePromotionScope] objects tracking the |
+/// program's nesting structure. Whenever a variable is read, the current |
+/// [TypePromotionFact] and [TypePromotionScope] are recorded for later use. |
+/// |
+/// During type inference, the [TypeInferrer] calls back into this class to ask |
+/// whether each variable read is a promoted read. This is determined by |
+/// examining the [TypePromotionScope] and [TypePromotionFact] objects that were |
+/// recorded at the time the variable read was parsed, as well as other state |
+/// that may have been updated later during the parsing process. |
+/// |
+/// This class abstracts away the representation of the underlying AST using |
+/// generic parameters. Derived classes should set E and V to the class they |
+/// use to represent expressions and variable declarations, respectively. |
+abstract class TypePromoter<E, V> { |
+ /// Returns the current type promotion scope. |
+ TypePromotionScope get currentScope; |
+ |
+ /// Computes the promoted type of a variable read having the given [fact] and |
+ /// [scope]. Returns `null` if there is no promotion. |
+ /// |
+ /// [mutatedInClosure] indicates whether the variable was mutated in a closure |
+ /// somewhere in the method. |
+ DartType computePromotedType(TypePromotionFact<V> fact, |
+ TypePromotionScope scope, bool mutatedInClosure); |
+ |
+ /// Updates the state to reflect the fact that we are entering an "else" |
+ /// branch. |
+ void enterElse(); |
+ |
+ /// Updates the state to reflect the fact that the "condition" part of an "if" |
+ /// statement or conditional expression has just been parsed, and we are |
+ /// entering the "then" branch. |
+ void enterThen(E condition); |
+ |
+ /// Updates the state to reflect the fact that we have exited the "else" |
+ /// branch of an "if" statement or conditional expression. |
+ void exitConditional(); |
+ |
+ /// Verifies that enter/exit calls were properly nested. |
+ void finished(); |
+ |
+ /// Records that the given [variable] was accessed for reading, and returns a |
+ /// [TypePromotionFact] describing the variable's current type promotion |
+ /// state. |
+ /// |
+ /// [functionNestingLevel] should be the current nesting level of closures. |
+ /// This is used to determine if the variable was accessed in a closure. |
+ TypePromotionFact<V> getFactForAccess(V variable, int functionNestingLevel); |
+ |
+ /// Updates the state to reflect the fact that an "is" check of a local |
+ /// variable was just parsed. |
+ void handleIsCheck(E isExpression, bool isInverted, V variable, DartType type, |
+ int functionNestingLevel); |
+ |
+ /// Updates the state to reflect the fact that the given [variable] was |
+ /// mutated. |
+ void mutateVariable(V variable, int functionNestingLevel); |
+} |
+ |
+/// Derived class containing generic implementations of [TypePromoter]. |
+/// |
+/// This class contains as much of the implementation of type promotion as |
+/// possible without knowing the identity of the type parameters. It defers to |
+/// abstract methods for everything else. |
+abstract class TypePromoterImpl<E, V> extends TypePromoter<E, V> { |
ahe
2017/04/25 13:17:01
Can this class be merged with TypePromoter to take
Paul Berry
2017/04/25 15:49:26
It could, but it would blur the interface boundary
|
+ /// [TypePromotionFact] representing the initial state (no facts have been |
+ /// determined yet). |
+ /// |
+ /// All linked lists of facts terminate in this object. |
+ final _NullFact<V> _nullFacts; |
+ |
+ /// Map from variable declaration to the most recent [TypePromotionFact] |
+ /// associated with the variable. |
+ /// |
+ /// [TypePromotionFact]s that are not associated with any variable show up in |
+ /// this map under the key `null`. |
+ final _factCache = <V, TypePromotionFact<V>>{}; |
+ |
+ /// Linked list of [TypePromotionFact]s that was current at the time the |
+ /// [_factCache] was last updated. |
+ TypePromotionFact<V> _factCacheState; |
+ |
+ /// Linked list of [TypePromotionFact]s describing what is known to be true |
+ /// after execution of the expression or statement that was most recently |
+ /// parsed. |
+ TypePromotionFact<V> _currentFacts; |
+ |
+ /// The most recently parsed expression whose outcome potentially affects what |
+ /// is known to be true (e.g. an "is" check or a logical expression). May be |
+ /// `null` if no such expression has been encountered yet. |
+ E _promotionExpression; |
+ |
+ /// Linked list of [TypePromotionFact]s describing what is known to be true |
+ /// after execution of [_promotionExpression], assuming that |
+ /// [_promotionExpression] evaluates to `true`. |
+ TypePromotionFact<V> _trueFactsForPromotionExpression; |
+ |
+ /// Linked list of [TypePromotionScope]s describing the nesting structure that |
+ /// contains the expressoin or statement that was most recently parsed. |
+ TypePromotionScope _currentScope = const _TopLevelScope(); |
+ |
+ /// The sequence number of the [TypePromotionFact] that was most recently |
+ /// created. |
+ int _lastFactSequenceNumber = 0; |
+ |
+ TypePromoterImpl() : this._(new _NullFact<V>()); |
+ |
+ TypePromoterImpl._(_NullFact<V> this._nullFacts) |
+ : _factCacheState = _nullFacts, |
+ _currentFacts = _nullFacts { |
+ _factCache[null] = _nullFacts; |
+ } |
+ |
+ @override |
+ TypePromotionScope get currentScope => _currentScope; |
+ |
+ /// Returns a map from variable declaration to the most recent |
+ /// [TypePromotionFact] associated with the variable. |
+ Map<V, TypePromotionFact<V>> get _currentFactMap { |
ahe
2017/04/25 13:17:01
This is a potentially expensive operation, and in
Paul Berry
2017/04/25 15:49:26
Done.
|
+ // Roll back any map entries associated with facts that are no longer in |
+ // effect, and set [commonAncestor] to the fact that is an ancestor of |
+ // the current state and the previously cached state. To do this, we set a |
+ // variable pointing to [_currentFacts], and then walk both it and |
+ // [_factCacheState] back to their common ancestor, updating [_factCache] as |
+ // we go. |
+ TypePromotionFact<V> commonAncestor = _currentFacts; |
+ while (commonAncestor.sequenceNumber != _factCacheState.sequenceNumber) { |
+ if (commonAncestor.sequenceNumber > _factCacheState.sequenceNumber) { |
+ // The currently cached state is older than the common ancestor guess, |
+ // so the common ancestor guess needs to be walked back. |
+ commonAncestor = commonAncestor.previous; |
+ } else { |
+ // The common ancestor guess is older than the currently cached state, |
+ // so we need to roll back the map entry associated with the currently |
+ // cached state. |
+ _factCache[_factCacheState.variable] = |
+ _factCacheState.previousForVariable; |
+ _factCacheState = _factCacheState.previous; |
+ } |
+ } |
+ assert(identical(commonAncestor, _factCacheState)); |
+ // Roll forward any map entries associated with facts that are newly in |
+ // effect. Since newer facts link to older ones, it is easiest to do roll |
+ // forward the most recent facts first. |
+ for (TypePromotionFact<V> newState = _currentFacts; |
+ !identical(newState, commonAncestor); |
+ newState = newState.previous) { |
+ var currentlyCached = _factCache[newState.variable]; |
+ // Note: Since we roll forward the most recent facts first, we need to be |
+ // careful not write an older fact over a newer one. |
+ if (currentlyCached == null || |
+ newState.sequenceNumber > currentlyCached.sequenceNumber) { |
+ _factCache[newState.variable] = newState; |
+ } |
+ } |
+ _factCacheState = _currentFacts; |
+ return _factCache; |
+ } |
+ |
+ @override |
+ DartType computePromotedType(TypePromotionFact<V> fact, |
+ TypePromotionScope scope, bool mutatedInClosure) { |
+ if (mutatedInClosure) return null; |
+ return fact?._computePromotedType(this, scope); |
+ } |
+ |
+ @override |
+ void enterElse() { |
+ _debugEvent('enterElse'); |
+ _ConditionalScope scope = _currentScope; |
+ // Record the current fact state so that once we exit the "else" branch, we |
+ // can merge facts from the two branches. |
+ scope.afterTrue = _currentFacts; |
+ // While processing the "else" block, assume the condition was false. |
+ _currentFacts = scope.beforeElse; |
+ } |
+ |
+ @override |
+ void enterThen(E condition) { |
+ _debugEvent('enterThen'); |
+ // Figure out what the facts are based on possible condition outcomes. |
+ var trueFacts = _trueFacts(condition); |
+ var falseFacts = _falseFacts(condition); |
+ // Record the fact that we are entering a new scope, and save the "false" |
+ // facts for when we enter the "else" branch. |
+ _currentScope = new _ConditionalScope(_currentScope, falseFacts); |
+ // While processing the "then" block, assume the condition was true. |
+ _currentFacts = trueFacts; |
+ } |
+ |
+ @override |
+ void exitConditional() { |
+ _debugEvent('exitConditional'); |
+ _ConditionalScope scope = _currentScope; |
+ _currentScope = _currentScope._enclosing; |
+ _currentFacts = _mergeFacts(scope.afterTrue, _currentFacts); |
+ } |
+ |
+ @override |
+ void finished() { |
+ _debugEvent('finished'); |
+ if (_currentScope is! _TopLevelScope) { |
+ internalError('Stack not empty'); |
+ } |
+ } |
+ |
+ @override |
+ TypePromotionFact<V> getFactForAccess(V variable, int functionNestingLevel) { |
+ _debugEvent('getFactForAccess'); |
+ var fact = _currentFactMap[variable]; |
+ TypePromotionFact._accessedInScope( |
+ fact, _currentScope, functionNestingLevel); |
+ return fact; |
+ } |
+ |
+ /// Returns the nesting level that was in effect when [variable] was declared. |
+ int getVariableFunctionNestingLevel(V variable); |
+ |
+ @override |
+ void handleIsCheck(E isExpression, bool isInverted, V variable, DartType type, |
+ int functionNestingLevel) { |
+ _debugEvent('handleIsCheck'); |
+ var isCheck = new _IsCheck<V>(++_lastFactSequenceNumber, variable, |
+ _currentFacts, _currentFactMap[variable], functionNestingLevel, type); |
+ if (isInverted) { |
+ _recordPromotionExpression(isExpression, _currentFacts, isCheck); |
+ } else { |
+ _recordPromotionExpression(isExpression, isCheck, _currentFacts); |
+ } |
+ } |
+ |
+ /// Updates the state to reflect the fact that the given [variable] was |
+ /// mutated. |
+ void mutateVariable(V variable, int functionNestingLevel) { |
+ _debugEvent('mutateVariable'); |
+ var fact = _currentFactMap[variable]; |
+ TypePromotionFact._mutatedInScope(fact, _currentScope); |
+ if (getVariableFunctionNestingLevel(variable) < functionNestingLevel) { |
+ setVariableMutatedInClosure(variable); |
+ } |
+ setVariableMutatedAnywhere(variable); |
+ } |
+ |
+ /// Determines whether [a] and [b] represent the same expression, after |
+ /// dropping redundant enclosing parentheses. |
+ bool sameExpressions(E a, E b); |
+ |
+ /// Records that the given variable was mutated somewhere inside the method. |
+ void setVariableMutatedAnywhere(V variable); |
+ |
+ /// Records that the given variable was mutated inside a closure. |
+ void setVariableMutatedInClosure(V variable); |
+ |
+ /// Indicates whether [setVariableMutatedAnywhere] has been called for the |
+ /// given [variable]. |
+ bool wasVariableMutatedAnywhere(V variable); |
+ |
+ /// For internal debugging use, optionally prints the current state followed |
+ /// by the event name. Uncomment the call to [_printEvent] to see the |
+ /// sequence of calls into the type promoter and the corresponding states. |
+ void _debugEvent(String name) { |
ahe
2017/04/25 13:17:01
If you make this public, it also works across subc
Paul Berry
2017/04/25 15:49:26
Done.
|
+ // _printEvent(name); |
+ } |
+ |
+ /// Returns the set of facts known to be true after the execution of [e] |
+ /// assuming it evaluates to `false`. |
+ /// |
+ /// [e] must be the most resently parsed expression or statement. |
+ TypePromotionFact<V> _falseFacts(E e) { |
ahe
2017/04/25 13:17:01
How about factsWhenFalse (as to not imply that the
Paul Berry
2017/04/25 15:49:26
Done.
|
+ // Type promotion currently only occurs when an "is" or logical expression |
+ // evaluates to `true`, so no special logic is required; we just use |
+ // [_currentFacts]. |
+ // |
+ // TODO(paulberry): experiment with supporting promotion in cases like |
+ // `if (x is! T) { ... } else { ...access x... }` |
+ return _currentFacts; |
+ } |
+ |
+ /// Returns the set of facts known to be true after two branches of execution |
+ /// rejoin. |
+ TypePromotionFact<V> _mergeFacts( |
+ TypePromotionFact<V> a, TypePromotionFact<V> b) { |
+ // Type promotion currently doesn't support any mechanism for facts to |
+ // accumulate along a straight-line execution path (they can only accumulate |
+ // when entering a scope), so we can simply find the common ancestor fact. |
+ // |
+ // TODO(paulberry): experiment with supporting promotion in cases like: |
+ // if (...) { |
+ // if (x is! T) return; |
+ // } else { |
+ // if (x is! T) return; |
+ // } |
+ // ...access x... |
+ while (a.sequenceNumber != b.sequenceNumber) { |
+ if (a.sequenceNumber > b.sequenceNumber) { |
+ a = a.previous; |
+ } else { |
+ b = b.previous; |
+ } |
+ } |
+ assert(identical(a, b)); |
+ return a; |
+ } |
+ |
+ /// For internal debugging use, prints the current state followed by the event |
+ /// name. |
+ void _printEvent(String name) { |
+ Iterable<TypePromotionFact<V>> factChain(TypePromotionFact<V> fact) sync* { |
+ while (fact != null) { |
+ yield fact; |
+ fact = fact.previousForVariable; |
+ } |
+ } |
+ |
+ _currentFactMap.forEach((variable, fact) { |
+ if (fact == null) return; |
+ print(' ${variable ?? '(null)'}: ${factChain(fact).join(' -> ')}'); |
+ }); |
+ print(name); |
+ } |
+ |
+ /// Records that after the evaluation of [expression], the facts will be |
+ /// [ifTrue] on a branch where the expression evaluted to `true`, and |
+ /// [ifFalse] on a branch where the expression evaluated to `false` (or where |
+ /// the truth value of the expresison doesn't matter). |
+ /// |
+ /// TODO(paulberry): when we start handling promotion in "else" clauses, we'll |
+ /// need to split [ifFalse] into two cases, one for when the expression |
+ /// evaluated to `false`, and one where the truth value of the expression |
+ /// doesn't matter. |
+ void _recordPromotionExpression( |
+ E expression, TypePromotionFact<V> ifTrue, TypePromotionFact<V> ifFalse) { |
+ _promotionExpression = expression; |
+ _trueFactsForPromotionExpression = ifTrue; |
+ _currentFacts = ifFalse; |
+ } |
+ |
+ /// Returns the set of facts known to be true after the execution of [e] |
+ /// assuming it evaluates to `true`. |
+ /// |
+ /// [e] must be the most resently parsed expression or statement. |
+ TypePromotionFact<V> _trueFacts(E e) => |
+ sameExpressions(_promotionExpression, e) |
+ ? _trueFactsForPromotionExpression |
+ : _currentFacts; |
+} |
+ |
+/// A single fact which is known to the type promotion engine about the state of |
+/// a variable (or about the flow control of the program). |
+/// |
+/// The type argument V represents is the class which represents local variable |
+/// declarations. |
+/// |
+/// Facts are linked together into linked lists via the [previous] pointer into |
+/// a data structure called a "fact chain" (or sometimes a "fact state"), which |
+/// represents all facts that are known to hold at a certain point in the |
+/// program. |
+/// |
+/// The fact is said to "apply" to a given point in the execution of the program |
+/// if the fact is part of the current fact state at the point the parser |
+/// reaches that point in the program. |
ahe
2017/04/25 13:17:01
I wonder how this definition of "apply" correspond
Paul Berry
2017/04/25 15:49:26
Yeah, that deserves some clarification. The idea
|
+abstract class TypePromotionFact<V> { |
+ /// The variable this fact records information about, or `null` if this fact |
+ /// records information about general flow control. |
+ final V variable; |
+ |
+ /// The fact chain that was in effect prior to execution of the statement or |
+ /// expression that caused this fact to be true. |
+ final TypePromotionFact<V> previous; |
+ |
+ /// Integer associated with this fact. Each time a fact is created it is |
+ /// given a sequence number one greater than the previously generated fact. |
+ /// This simplifies the algorithm for finding the common ancestor of two |
+ /// facts; we repeatedly walk backward the fact with the larger sequence |
+ /// number until the sequence numbers are the same. |
+ final int sequenceNumber; |
+ |
+ /// The most recent fact appearing in the fact chain [previous] whose |
+ /// [variable] matches this one, or `null` if there is no such fact. |
+ final TypePromotionFact<V> previousForVariable; |
+ |
+ /// The function nesting level of the expression that led to this fact. |
+ final int functionNestingLevel; |
+ |
+ /// If this fact's variable was mutated within any scopes the |
+ /// fact applies to, a set of the corresponding scopes. Otherwise `null`. |
+ /// |
+ /// TODO(paulberry): the size of this set is probably very small most of the |
+ /// time. Would it be better to use a list? |
+ Set<TypePromotionScope> _mutatedInScopes; |
+ |
+ /// If this fact's variable was accessed inside a closure within any scopes |
+ /// the fact applies to, a set of the corresponding scopes. Otherwise `null`. |
+ /// |
+ /// TODO(paulberry): the size of this set is probably very small most of the |
+ /// time. Would it be better to use a list? |
+ Set<TypePromotionScope> _accessedInClosureInScopes; |
+ |
+ TypePromotionFact(this.sequenceNumber, this.variable, this.previous, |
+ this.previousForVariable, this.functionNestingLevel); |
+ |
+ /// Computes the promoted type for [variable] at a location in the code where |
+ /// this fact applies. |
+ /// |
+ /// Should not be called until after parsing of the entire method is complete. |
+ DartType _computePromotedType( |
+ TypePromoterImpl<dynamic, V> promoter, TypePromotionScope scope); |
+ |
+ /// Records the fact that the variable referenced by [fact] was accessed |
+ /// within the given scope, at the given function nesting level. |
+ /// |
+ /// If `null` is passed in for [fact], there is no effect. |
+ static void _accessedInScope(TypePromotionFact fact, TypePromotionScope scope, |
ahe
2017/04/25 13:17:00
Consider changing name of function to "recordAcces
Paul Berry
2017/04/25 15:49:27
Done.
|
+ int functionNestingLevel) { |
+ // TODO(paulberry): make some integration test cases that exercise the |
+ // behaviors of this function. In particular verify that it's correct to |
+ // test functionNestingLevel against fact.functionNestingLevel (as opposed |
+ // to testing it against getVariableFunctionNestingLevel(variable)). |
+ while (fact != null) { |
+ if (functionNestingLevel > fact.functionNestingLevel) { |
+ fact._accessedInClosureInScopes ??= |
+ new Set<TypePromotionScope>.identity(); |
+ if (!fact._accessedInClosureInScopes.add(scope)) return; |
+ } |
+ fact = fact.previousForVariable; |
+ } |
+ } |
+ |
+ /// Records the fact that the variable referenced by [fact] was mutated |
+ /// within the given scope. |
+ /// |
+ /// If `null` is passed in for [fact], there is no effect. |
+ static void _mutatedInScope( |
ahe
2017/04/25 13:17:00
Ditto.
Paul Berry
2017/04/25 15:49:27
Done.
|
+ TypePromotionFact fact, TypePromotionScope scope) { |
+ while (fact != null) { |
+ fact._mutatedInScopes ??= new Set<TypePromotionScope>.identity(); |
+ if (!fact._mutatedInScopes.add(scope)) return; |
+ fact = fact.previousForVariable; |
+ } |
+ } |
+} |
+ |
+/// Represents a contiguous block of program text in which variables may or may |
+/// not be promoted. Also used as a stack to keep track of state while the |
+/// method is being parsed. |
+class TypePromotionScope { |
+ /// The nesting depth of this scope. The outermost scope (representing the |
+ /// whole method body) has a depth of 0. |
+ final int _depth; |
+ |
+ /// The [TypePromotionScope] representing the scope enclosing this one. |
+ final TypePromotionScope _enclosing; |
+ |
+ TypePromotionScope(this._enclosing) : _depth = _enclosing._depth + 1; |
+ |
+ const TypePromotionScope._topLevel() |
+ : _enclosing = null, |
+ _depth = 0; |
+ |
+ /// Determines whether this scope completely encloses (or is the same as) |
+ /// [other]. |
+ bool containsScope(TypePromotionScope other) { |
+ if (this._depth > other._depth) { |
+ // We can't possibly contain a scope if we are at greater nesting depth |
+ // than it is. |
+ return false; |
+ } |
+ while (this._depth < other._depth) { |
+ other = other._enclosing; |
+ } |
+ return identical(this, other); |
+ } |
+} |
+ |
+/// [TypePromotionScope] representing the "then" and "else" bodies of an "if" |
+/// statement or conditional expression. |
+class _ConditionalScope<V> extends TypePromotionScope { |
+ /// The fact state in effect at the top of the "else" block. |
+ final TypePromotionFact<V> beforeElse; |
+ |
+ /// The fact state which was in effect at the bottom of the "then" block. |
+ TypePromotionFact<V> afterTrue; |
+ |
+ _ConditionalScope(TypePromotionScope enclosing, this.beforeElse) |
+ : super(enclosing); |
+} |
+ |
+/// [TypePromotionFact] representing an "is" check which succeeded. |
+class _IsCheck<V> extends TypePromotionFact<V> { |
+ /// The type appearing on the right hand side of "is". |
+ final DartType checkedType; |
+ |
+ _IsCheck( |
+ int sequenceNumber, |
+ V variable, |
+ TypePromotionFact<V> previous, |
+ TypePromotionFact<V> previousForVariable, |
+ int functionNestingLevel, |
+ this.checkedType) |
+ : super(sequenceNumber, variable, previous, previousForVariable, |
+ functionNestingLevel); |
+ |
+ @override |
+ String toString() => 'isCheck($checkedType)'; |
+ |
+ @override |
+ DartType _computePromotedType( |
+ TypePromoterImpl<dynamic, V> promoter, TypePromotionScope scope) { |
+ // TODO(paulberry): add a subtype check. For example: |
+ // f(Object x) { |
+ // if (x is int) { // promotes x to int |
+ // if (x is String) { // does not promote x to String, since String |
+ // // not a subtype of int |
+ // } |
+ // } |
+ // } |
+ |
+ // If the variable was mutated somewhere in the scope of the potential |
+ // promotion, promotion does not occur. |
+ if (_mutatedInScopes != null) { |
+ for (var assignmentScope in _mutatedInScopes) { |
+ if (assignmentScope.containsScope(scope)) { |
+ return previousForVariable?._computePromotedType(promoter, scope); |
+ } |
+ } |
+ } |
+ |
+ // If the variable was mutated anywhere, and it was accessed inside a |
+ // closure somewhere in the scope of the potential promotion, promotion does |
+ // not occur. |
+ if (promoter.wasVariableMutatedAnywhere(variable) && |
+ _accessedInClosureInScopes != null) { |
+ for (var accessScope in _accessedInClosureInScopes) { |
+ if (accessScope.containsScope(scope)) { |
+ return previousForVariable?._computePromotedType(promoter, scope); |
+ } |
+ } |
+ } |
+ return checkedType; |
+ } |
+} |
+ |
+/// Instance of [TypePromotionFact] representing the facts which are known on |
+/// entry to the method (i.e. nothing). |
+class _NullFact<V> extends TypePromotionFact<V> { |
+ _NullFact() : super(0, null, null, null, 0); |
+ |
+ @override |
+ String toString() => 'null'; |
+ |
+ @override |
+ DartType _computePromotedType( |
+ TypePromoter<dynamic, V> promoter, TypePromotionScope scope) { |
+ throw new StateError('Tried to create promoted type for no variable'); |
+ } |
+} |
+ |
+/// Instance of [TypePromotionScope] representing the entire method body. |
+class _TopLevelScope extends TypePromotionScope { |
+ const _TopLevelScope() : super._topLevel(); |
+} |