Index: pkg/compiler/lib/src/cps_ir/type_propagation.dart |
diff --git a/pkg/compiler/lib/src/cps_ir/type_propagation.dart b/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
index b9db352a20ab40db6ae8c897b9f680390835d5bb..75a473e891ee3d213d14b37aa177069b73748ae6 100644 |
--- a/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
+++ b/pkg/compiler/lib/src/cps_ir/type_propagation.dart |
@@ -25,6 +25,7 @@ abstract class TypeSystem<T> { |
T get functionType; |
T get boolType; |
T get intType; |
+ T get numType; |
T get stringType; |
T get listType; |
T get mapType; |
@@ -44,6 +45,8 @@ abstract class TypeSystem<T> { |
bool isDefinitelyBool(T type); |
bool isDefinitelyNotNull(T type); |
+ |
+ AbstractBool isSubtypeOf(T value, types.DartType type, {bool allowNull}); |
} |
class UnitTypeSystem implements TypeSystem<String> { |
@@ -53,6 +56,7 @@ class UnitTypeSystem implements TypeSystem<String> { |
get dynamicType => UNIT; |
get functionType => UNIT; |
get intType => UNIT; |
+ get numType => UNIT; |
get listType => UNIT; |
get mapType => UNIT; |
get stringType => UNIT; |
@@ -67,15 +71,23 @@ class UnitTypeSystem implements TypeSystem<String> { |
exact(_) => UNIT; |
getTypeOf(_) => UNIT; |
+ bool areDisjoint(String leftType, String rightType) { |
+ return false; |
+ } |
+ |
bool isDefinitelyBool(_) => false; |
bool isDefinitelyNotNull(_) => false; |
- bool areDisjoint(String leftType, String rightType) { |
- return false; |
+ AbstractBool isSubtypeOf(value, type, {bool allowNull}) { |
+ return AbstractBool.Maybe; |
} |
} |
+enum AbstractBool { |
+ True, False, Maybe, Nothing |
+} |
+ |
class TypeMaskSystem implements TypeSystem<TypeMask> { |
final TypesTask inferrer; |
final ClassWorld classWorld; |
@@ -85,6 +97,7 @@ class TypeMaskSystem implements TypeSystem<TypeMask> { |
TypeMask get functionType => inferrer.functionType; |
TypeMask get boolType => inferrer.boolType; |
TypeMask get intType => inferrer.intType; |
+ TypeMask get numType => inferrer.numType; |
TypeMask get stringType => inferrer.stringType; |
TypeMask get listType => inferrer.listType; |
TypeMask get mapType => inferrer.mapType; |
@@ -140,6 +153,149 @@ class TypeMaskSystem implements TypeSystem<TypeMask> { |
TypeMask intersection = leftType.intersection(rightType, classWorld); |
return intersection.isEmpty && !intersection.isNullable; |
} |
+ |
+ AbstractBool isSubtypeOf(TypeMask value, |
+ types.DartType type, |
+ {bool allowNull}) { |
+ assert(allowNull != null); |
+ if (type is types.DynamicType) { |
+ if (!allowNull && value.isNullable) return AbstractBool.Maybe; |
+ return AbstractBool.True; |
+ } |
+ if (type is types.InterfaceType) { |
+ TypeMask typeAsMask = allowNull |
+ ? new TypeMask.subtype(type.element, classWorld) |
+ : new TypeMask.nonNullSubtype(type.element, classWorld); |
+ if (areDisjoint(value, typeAsMask)) { |
+ // Disprove the subtype relation based on the class alone. |
+ return AbstractBool.False; |
+ } |
+ if (!type.treatAsRaw) { |
+ // If there are type arguments, we cannot prove the subtype relation, |
+ // because the type arguments are unknown on both the value and type. |
+ return AbstractBool.Maybe; |
+ } |
+ if (typeAsMask.containsMask(value, classWorld)) { |
+ // All possible values are contained in the set of allowed values. |
+ // Note that we exploit the fact that [typeAsMask] is an exact |
+ // representation of [type], not an approximation. |
+ return AbstractBool.True; |
+ } |
+ // The value is neither contained in the type, nor disjoint from the type. |
+ return AbstractBool.Maybe; |
+ } |
+ // TODO(asgerf): Support function types, and what else might be missing. |
+ return AbstractBool.Maybe; |
+ } |
+} |
+ |
+class ConstantPropagationLattice<T> { |
+ final TypeSystem<T> typeSystem; |
+ final ConstantSystem constantSystem; |
+ final types.DartTypes dartTypes; |
+ |
+ ConstantPropagationLattice(this.typeSystem, |
+ this.constantSystem, |
+ this.dartTypes); |
+ |
+ final _AbstractValue<T> nothing = new _AbstractValue<T>.nothing(); |
+ |
+ _AbstractValue<T> constant(ConstantValue value) { |
+ return new _AbstractValue<T>.constantValue(value, |
+ typeSystem.getTypeOf(value)); |
+ } |
+ |
+ _AbstractValue<T> nonConstant(T type) { |
+ return new _AbstractValue<T>.nonConstant(type); |
+ } |
+ |
+ _AbstractValue<T> get anything { |
+ return new _AbstractValue<T>.nonConstant(typeSystem.dynamicType); |
+ } |
+ |
+ /// Returns whether the given [value] is an instance of [type]. |
+ /// |
+ /// Since [value] and [type] are not always known, [AbstractBool.Maybe] is |
+ /// returned if the answer is not known. |
+ /// |
+ /// [AbstractBool.Nothing] is returned if [value] is nothing. |
+ /// |
+ /// If [allowNull] is true, `null` is considered to an instance of anything, |
+ /// otherwise it is only considered an instance of [Object], [dynamic], and |
+ /// [Null]. |
+ AbstractBool isSubtypeOf(_AbstractValue<T> value, |
+ types.DartType type, |
+ {bool allowNull}) { |
+ assert(allowNull != null); |
+ if (value.isNothing) { |
+ return AbstractBool.Nothing; |
+ } |
+ if (value.isConstant) { |
+ if (value.constant.isNull) { |
+ if (allowNull || |
+ type.isObject || |
+ type.isDynamic || |
+ type == dartTypes.coreTypes.nullType) { |
+ return AbstractBool.True; |
+ } |
+ if (type is types.TypeVariableType) { |
+ return AbstractBool.Maybe; |
+ } |
+ return AbstractBool.False; |
+ } |
+ types.DartType valueType = value.constant.getType(dartTypes.coreTypes); |
+ if (constantSystem.isSubtype(dartTypes, valueType, type)) { |
+ return AbstractBool.True; |
+ } |
+ if (!dartTypes.isPotentialSubtype(valueType, type)) { |
+ return AbstractBool.False; |
+ } |
+ return AbstractBool.Maybe; |
+ } |
+ return typeSystem.isSubtypeOf(value.type, type, allowNull: allowNull); |
+ } |
+ |
+ /// Returns the possible results of applying [operator] to [value], |
+ /// assuming the operation does not throw. |
+ /// |
+ /// Because we do not explicitly track thrown values, we currently use the |
+ /// convention that constant values are returned from this method only |
+ /// if the operation is known not to throw. |
+ _AbstractValue<T> unaryOp(UnaryOperator operator, |
+ _AbstractValue<T> value) { |
+ // TODO(asgerf): Also return information about whether this can throw? |
+ if (value.isNothing) { |
+ return nothing; |
+ } |
+ if (value.isConstant) { |
+ UnaryOperation operation = constantSystem.lookupUnary(operator); |
+ ConstantValue result = operation.fold(value.constant); |
+ if (result == null) return anything; |
+ return constant(result); |
+ } |
+ return anything; // TODO(asgerf): Look up type. |
+ } |
+ |
+ /// Returns the possible results of applying [operator] to [left], [right], |
+ /// assuming the operation does not throw. |
+ /// |
+ /// Because we do not explicitly track thrown values, we currently use the |
+ /// convention that constant values are returned from this method only |
+ /// if the operation is known not to throw. |
+ _AbstractValue<T> binaryOp(BinaryOperator operator, |
+ _AbstractValue<T> left, |
+ _AbstractValue<T> right) { |
+ if (left.isNothing || right.isNothing) { |
+ return nothing; |
+ } |
+ if (left.isConstant && right.isConstant) { |
+ BinaryOperation operation = constantSystem.lookupBinary(operator); |
+ ConstantValue result = operation.fold(left.constant, right.constant); |
+ if (result == null) return anything; |
+ return constant(result); |
+ } |
+ return anything; // TODO(asgerf): Look up type. |
+ } |
} |
/** |
@@ -162,35 +318,41 @@ class TypePropagator<T> extends Pass { |
final ConstantSystem _constantSystem; |
final TypeSystem _typeSystem; |
final dart2js.InternalErrorFunction _internalError; |
- final Map<Node, _AbstractValue> _types; |
+ final Map<Primitive, _AbstractValue> _types = <Primitive, _AbstractValue>{}; |
TypePropagator(this._dartTypes, |
this._constantSystem, |
this._typeSystem, |
- this._internalError) |
- : _types = <Node, _AbstractValue>{}; |
+ this._internalError); |
@override |
void rewrite(FunctionDefinition root) { |
// Set all parent pointers. |
new ParentVisitor().visit(root); |
+ ConstantPropagationLattice<T> lattice = new ConstantPropagationLattice<T>( |
+ _typeSystem, _constantSystem, _dartTypes); |
+ Map<Expression, ConstantValue> replacements = <Expression, ConstantValue>{}; |
+ |
// Analyze. In this phase, the entire term is analyzed for reachability |
// and the abstract value of each expression. |
_TypePropagationVisitor<T> analyzer = new _TypePropagationVisitor<T>( |
- _constantSystem, |
- _typeSystem, |
+ lattice, |
_types, |
- _internalError, |
- _dartTypes); |
+ replacements, |
+ _internalError); |
analyzer.analyze(root); |
// Transform. Uses the data acquired in the previous analysis phase to |
// replace branches with fixed targets and side-effect-free expressions |
- // with constant results. |
+ // with constant results or existing values that are in scope. |
_TransformingVisitor<T> transformer = new _TransformingVisitor<T>( |
- analyzer.reachableNodes, analyzer.values, _internalError, _typeSystem); |
+ lattice, |
+ analyzer.reachableNodes, |
+ analyzer.values, |
+ replacements, |
+ _internalError); |
transformer.transform(root); |
} |
@@ -204,49 +366,52 @@ class TypePropagator<T> extends Pass { |
class _TransformingVisitor<T> extends RecursiveVisitor { |
final Set<Node> reachable; |
final Map<Node, _AbstractValue> values; |
- final TypeSystem<T> typeSystem; |
+ final Map<Expression, ConstantValue> replacements; |
+ final ConstantPropagationLattice<T> valueLattice; |
+ |
+ TypeSystem<T> get typeSystem => valueLattice.typeSystem; |
final dart2js.InternalErrorFunction internalError; |
- _TransformingVisitor(this.reachable, |
+ _TransformingVisitor(this.valueLattice, |
+ this.reachable, |
this.values, |
- this.internalError, |
- this.typeSystem); |
+ this.replacements, |
+ this.internalError); |
void transform(FunctionDefinition root) { |
visit(root); |
} |
+ Constant makeConstantPrimitive(ConstantValue constant) { |
+ ConstantExpression constExp = |
+ const ConstantExpressionCreator().convert(constant); |
+ Constant primitive = new Constant(constExp, constant); |
+ values[primitive] = new _AbstractValue.constantValue(constant, |
+ typeSystem.getTypeOf(constant)); |
+ return primitive; |
+ } |
+ |
/// Given an expression with a known constant result and a continuation, |
/// replaces the expression by a new LetPrim / InvokeContinuation construct. |
/// `unlink` is a closure responsible for unlinking all removed references. |
LetPrim constifyExpression(Expression node, |
Continuation continuation, |
void unlink()) { |
- _AbstractValue value = values[node]; |
- if (value == null || !value.isConstant) { |
- return null; |
- } |
+ ConstantValue constant = replacements[node]; |
+ if (constant == null) return null; |
assert(continuation.parameters.length == 1); |
+ InteriorNode parent = node.parent; |
+ Constant primitive = makeConstantPrimitive(constant); |
+ LetPrim letPrim = new LetPrim(primitive); |
- // Set up the replacement structure. |
- PrimitiveConstantValue primitiveConstant = value.constant; |
- ConstantExpression constExp = |
- const ConstantExpressionCreator().convert(primitiveConstant); |
- Constant constant = new Constant(constExp, primitiveConstant); |
- LetPrim letPrim = new LetPrim(constant); |
InvokeContinuation invoke = |
- new InvokeContinuation(continuation, <Primitive>[constant]); |
- |
- invoke.parent = constant.parent = letPrim; |
+ new InvokeContinuation(continuation, <Primitive>[primitive]); |
+ parent.body = letPrim; |
letPrim.body = invoke; |
- |
- // Replace the method invocation. |
- |
- InteriorNode parent = node.parent; |
+ invoke.parent = letPrim; |
letPrim.parent = parent; |
- parent.body = letPrim; |
unlink(); |
@@ -334,20 +499,34 @@ class _TransformingVisitor<T> extends RecursiveVisitor { |
} |
} |
- // See [visitInvokeMethod]. |
- void visitTypeOperator(TypeOperator node) { |
+ void visitTypeCast(TypeCast node) { |
Continuation cont = node.continuation.definition; |
- LetPrim letPrim = constifyExpression(node, cont, () { |
- node.value.unlink(); |
- node.typeArguments.forEach((Reference ref) => ref.unlink()); |
- node.continuation.unlink(); |
- }); |
+ InteriorNode parent = node.parent; |
- if (letPrim == null) { |
- super.visitTypeOperator(node); |
- } else { |
- visitLetPrim(letPrim); |
+ _AbstractValue<T> value = getValue(node.value.definition); |
+ switch (valueLattice.isSubtypeOf(value, node.type, allowNull: true)) { |
+ case AbstractBool.Maybe: |
+ case AbstractBool.Nothing: |
+ break; |
+ |
+ case AbstractBool.True: |
+ // Cast always succeeds, replace it with InvokeContinuation. |
+ InvokeContinuation invoke = |
+ new InvokeContinuation.fromCall(node.continuation, node.value); |
+ parent.body = invoke; |
+ invoke.parent = parent; |
+ super.visitInvokeContinuation(invoke); |
+ return; |
+ |
+ case AbstractBool.False: |
+ // Cast always fails, remove unreachable continuation body. |
+ assert(!reachable.contains(cont)); |
+ RemovalVisitor.remove(cont.body); |
+ cont.body = new Unreachable()..parent = cont; |
+ break; |
} |
+ |
+ super.visitTypeCast(node); |
} |
_AbstractValue<T> getValue(Primitive primitive) { |
@@ -367,6 +546,24 @@ class _TransformingVisitor<T> extends RecursiveVisitor { |
left.substituteFor(node); |
} |
} |
+ |
+ void visitLetPrim(LetPrim node) { |
+ _AbstractValue<T> value = getValue(node.primitive); |
+ if (node.primitive is! Constant && value.isConstant) { |
+ // If the value is a known constant, compile it as a constant. |
+ Constant newPrim = makeConstantPrimitive(value.constant); |
+ LetPrim newLet = new LetPrim(newPrim); |
+ node.parent.body = newLet; |
+ newLet.body = node.body; |
+ node.body.parent = newLet; |
+ newLet.parent = node.parent; |
+ newPrim.substituteFor(node.primitive); |
+ RemovalVisitor.remove(node.primitive); |
+ visit(newLet.body); |
+ } else { |
+ super.visitLetPrim(node); |
+ } |
+ } |
} |
/** |
@@ -391,10 +588,10 @@ class _TypePropagationVisitor<T> implements Visitor { |
// since their lattice value has changed. |
final Set<Definition> defWorkset = new Set<Definition>(); |
- final ConstantSystem constantSystem; |
- final TypeSystem<T> typeSystem; |
+ final ConstantPropagationLattice<T> valueLattice; |
final dart2js.InternalErrorFunction internalError; |
- final types.DartTypes _dartTypes; |
+ |
+ TypeSystem get typeSystem => valueLattice.typeSystem; |
_AbstractValue<T> nothing = new _AbstractValue.nothing(); |
@@ -412,14 +609,16 @@ class _TypePropagationVisitor<T> implements Visitor { |
// Stores the current lattice value for nodes. Note that it contains not only |
// definitions as keys, but also expressions such as method invokes. |
// Access through [getValue] and [setValue]. |
- final Map<Node, _AbstractValue<T>> values; |
+ final Map<Primitive, _AbstractValue<T>> values; |
+ |
+ /// Expressions that invoke their call continuation with a constant value |
+ /// and without any side effects. These can be replaced by the constant. |
+ final Map<Expression, ConstantValue> replacements; |
- _TypePropagationVisitor(this.constantSystem, |
- TypeSystem typeSystem, |
+ _TypePropagationVisitor(this.valueLattice, |
this.values, |
- this.internalError, |
- this._dartTypes) |
- : this.typeSystem = typeSystem; |
+ this.replacements, |
+ this.internalError); |
void analyze(FunctionDefinition root) { |
reachableNodes.clear(); |
@@ -560,63 +759,54 @@ class _TypePropagationVisitor<T> implements Visitor { |
Continuation cont = node.continuation.definition; |
setReachable(cont); |
- /// Sets the value of both the current node and the target continuation |
- /// parameter. |
- void setValues(_AbstractValue<T> updateValue) { |
- setValue(node, updateValue); |
+ /// Sets the value of the target continuation parameter, and possibly |
+ /// try to replace the whole invocation with a constant. |
+ void setResult(_AbstractValue<T> updateValue, {bool canReplace: false}) { |
Parameter returnValue = cont.parameters[0]; |
setValue(returnValue, updateValue); |
+ if (canReplace && updateValue.isConstant) { |
+ replacements[node] = updateValue.constant; |
+ } else { |
+ // A previous iteration might have tried to replace this. |
+ replacements.remove(node); |
+ } |
} |
_AbstractValue<T> lhs = getValue(node.receiver.definition); |
if (lhs.isNothing) { |
return; // And come back later. |
- } else if (lhs.isNonConst) { |
- setValues(nonConstant(typeSystem.getSelectorReturnType(node.selector))); |
- return; |
- } else if (!node.selector.isOperator) { |
+ } |
+ if (!node.selector.isOperator) { |
// TODO(jgruber): Handle known methods on constants such as String.length. |
- setValues(nonConstant()); |
+ setResult(nonConstant(typeSystem.getSelectorReturnType(node.selector))); |
return; |
} |
+ // TODO(asgerf): Support constant folding on intercepted calls! |
+ |
// Calculate the resulting constant if possible. |
- ConstantValue result; |
+ _AbstractValue<T> result; |
String opname = node.selector.name; |
if (node.selector.argumentCount == 0) { |
// Unary operator. |
- |
if (opname == "unary-") { |
opname = "-"; |
} |
- UnaryOperation operation = constantSystem.lookupUnary( |
- UnaryOperator.parse(opname)); |
- if (operation != null) { |
- result = operation.fold(lhs.constant); |
- } |
+ UnaryOperator operator = UnaryOperator.parse(opname); |
+ result = valueLattice.unaryOp(operator, lhs); |
} else if (node.selector.argumentCount == 1) { |
// Binary operator. |
- |
_AbstractValue<T> rhs = getValue(node.arguments[0].definition); |
- if (!rhs.isConstant) { |
- setValues(nonConstant()); |
- return; |
- } |
- |
- BinaryOperation operation = constantSystem.lookupBinary( |
- BinaryOperator.parse(opname)); |
- if (operation != null) { |
- result = operation.fold(lhs.constant, rhs.constant); |
- } |
+ BinaryOperator operator = BinaryOperator.parse(opname); |
+ result = valueLattice.binaryOp(operator, lhs, rhs); |
} |
// Update value of the continuation parameter. Again, this is effectively |
// a phi. |
if (result == null) { |
- setValues(nonConstant()); |
+ setResult(nonConstant()); |
} else { |
- T type = typeSystem.getTypeOf(result); |
- setValues(constantValue(result, type)); |
+ setResult(result, canReplace: true); |
} |
} |
@@ -643,10 +833,17 @@ class _TypePropagationVisitor<T> implements Visitor { |
Continuation cont = node.continuation.definition; |
setReachable(cont); |
- void setValues(_AbstractValue<T> updateValue) { |
- setValue(node, updateValue); |
+ /// Sets the value of the target continuation parameter, and possibly |
+ /// try to replace the whole invocation with a constant. |
+ void setResult(_AbstractValue<T> updateValue, {bool canReplace: false}) { |
Parameter returnValue = cont.parameters[0]; |
setValue(returnValue, updateValue); |
+ if (canReplace && updateValue.isConstant) { |
+ replacements[node] = updateValue.constant; |
+ } else { |
+ // A previous iteration might have tried to replace this. |
+ replacements.remove(node); |
+ } |
} |
// TODO(jgruber): Currently we only optimize if all arguments are string |
@@ -670,9 +867,9 @@ class _TypePropagationVisitor<T> implements Visitor { |
}); |
LiteralDartString dartString = new LiteralDartString(allStrings.join()); |
ConstantValue constant = new StringConstantValue(dartString); |
- setValues(constantValue(constant, type)); |
+ setResult(constantValue(constant, type), canReplace: true); |
} else { |
- setValues(nonConstant(type)); |
+ setResult(nonConstant(type)); |
} |
} |
@@ -682,6 +879,9 @@ class _TypePropagationVisitor<T> implements Visitor { |
void visitRethrow(Rethrow node) { |
} |
+ void visitUnreachable(Unreachable node) { |
+ } |
+ |
void visitNonTailThrow(NonTailThrow node) { |
internalError(null, 'found non-tail throw after they were eliminated'); |
} |
@@ -709,50 +909,47 @@ class _TypePropagationVisitor<T> implements Visitor { |
} |
} |
- void visitTypeOperator(TypeOperator node) { |
- Continuation cont = node.continuation.definition; |
- setReachable(cont); |
+ void visitTypeTest(TypeTest node) { |
+ _AbstractValue<T> input = getValue(node.value.definition); |
+ T boolType = typeSystem.boolType; |
+ switch(valueLattice.isSubtypeOf(input, node.type, allowNull: false)) { |
+ case AbstractBool.Nothing: |
+ break; // And come back later. |
- void setValues(_AbstractValue<T> updateValue) { |
- setValue(node, updateValue); |
- Parameter returnValue = cont.parameters[0]; |
- setValue(returnValue, updateValue); |
- } |
+ case AbstractBool.True: |
+ setValue(node, constantValue(new TrueConstantValue(), boolType)); |
+ break; |
- if (node.isTypeCast) { |
- // TODO(jgruber): Add support for `as` casts. |
- setValues(nonConstant()); |
- return; |
+ case AbstractBool.False: |
+ setValue(node, constantValue(new FalseConstantValue(), boolType)); |
+ break; |
+ |
+ case AbstractBool.Maybe: |
+ setValue(node, nonConstant(boolType)); |
+ break; |
} |
+ } |
- _AbstractValue<T> cell = getValue(node.value.definition); |
- if (cell.isNothing) { |
- return; // And come back later. |
- } else if (cell.isConstant && node.type.kind == types.TypeKind.INTERFACE) { |
- // Receiver is a constant, perform is-checks at compile-time. |
- |
- types.InterfaceType checkedType = node.type; |
- ConstantValue constant = cell.constant; |
- types.DartType constantType = constant.getType(_dartTypes.coreTypes); |
- |
- T type = typeSystem.boolType; |
- _AbstractValue<T> result; |
- if (constant.isNull && |
- checkedType != _dartTypes.coreTypes.nullType && |
- checkedType != _dartTypes.coreTypes.objectType) { |
- // `(null is Type)` is true iff Type is in { Null, Object }. |
- result = constantValue(new FalseConstantValue(), type); |
- } else { |
- // Otherwise, perform a standard subtype check. |
- result = constantValue( |
- constantSystem.isSubtype(_dartTypes, constantType, checkedType) |
- ? new TrueConstantValue() |
- : new FalseConstantValue(), |
- type); |
- } |
- setValues(result); |
- } else { |
- setValues(nonConstant(typeSystem.boolType)); |
+ void visitTypeCast(TypeCast node) { |
+ Continuation cont = node.continuation.definition; |
+ _AbstractValue<T> input = getValue(node.value.definition); |
+ switch (valueLattice.isSubtypeOf(input, node.type, allowNull: true)) { |
+ case AbstractBool.Nothing: |
+ break; // And come back later. |
+ |
+ case AbstractBool.True: |
+ setReachable(cont); |
+ setValue(cont.parameters.single, input); |
+ break; |
+ |
+ case AbstractBool.False: |
+ break; // Cast fails. Continuation should remain unreachable. |
+ |
+ case AbstractBool.Maybe: |
+ // TODO(asgerf): Narrow type of output to those that survive the cast. |
+ setReachable(cont); |
+ setValue(cont.parameters.single, input); |
+ break; |
} |
} |