Version 2.11.0-197.0.dev Merge commit '0e9b2b8a365a53714aab1aac6a0820720ff93661' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart index 1a1c1df..637e387 100644 --- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart +++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -536,6 +536,25 @@ bool ifNullExpression_rightBegin( Expression leftHandSide, Type leftHandSideType); + /// Call this method before visiting the condition part of an if statement. + /// + /// The order of visiting an if statement with no "else" part should be: + /// - Call [ifStatement_conditionBegin] + /// - Visit the condition + /// - Call [ifStatement_thenBegin] + /// - Visit the "then" statement + /// - Call [ifStatement_end], passing `false` for `hasElse`. + /// + /// The order of visiting an if statement with an "else" part should be: + /// - Call [ifStatement_conditionBegin] + /// - Visit the condition + /// - Call [ifStatement_thenBegin] + /// - Visit the "then" statement + /// - Call [ifStatement_elseBegin] + /// - Visit the "else" statement + /// - Call [ifStatement_end], passing `true` for `hasElse`. + void ifStatement_conditionBegin(); + /// Call this method after visiting the "then" part of an if statement, and /// before visiting the "else" part. void ifStatement_elseBegin(); @@ -545,20 +564,6 @@ /// Call this method after visiting the condition part of an if statement. /// [condition] should be the if statement's condition. - /// - /// The order of visiting an if statement with no "else" part should be: - /// - Visit the condition - /// - Call [ifStatement_thenBegin] - /// - Visit the "then" statement - /// - Call [ifStatement_end], passing `false` for `hasElse`. - /// - /// The order of visiting an if statement with an "else" part should be: - /// - Visit the condition - /// - Call [ifStatement_thenBegin] - /// - Visit the "then" statement - /// - Call [ifStatement_elseBegin] - /// - Visit the "else" statement - /// - Call [ifStatement_end], passing `true` for `hasElse`. void ifStatement_thenBegin(Expression condition); /// Return whether the [variable] is definitely assigned in the current state. @@ -1015,6 +1020,12 @@ } @override + void ifStatement_conditionBegin() { + return _wrap('ifStatement_conditionBegin()', + () => _wrapped.ifStatement_conditionBegin()); + } + + @override void ifStatement_elseBegin() { return _wrap( 'ifStatement_elseBegin()', () => _wrapped.ifStatement_elseBegin()); @@ -1658,6 +1669,17 @@ FlowModel<Variable, Type> unsplit() => new FlowModel<Variable, Type>.withInfo(reachable.unsplit(), variableInfo); + /// Removes control flow splits until a [FlowModel] is obtained whose + /// reachability has the given [parent]. + FlowModel<Variable, Type> unsplitTo(Reachability parent) { + if (identical(this.reachable.parent, parent)) return this; + Reachability reachable = this.reachable.unsplit(); + while (!identical(reachable.parent, parent)) { + reachable = reachable.unsplit(); + } + return new FlowModel<Variable, Type>.withInfo(reachable, variableInfo); + } + /// Updates the state to indicate that an assignment was made to the given /// [variable]. The variable is marked as definitely assigned, and any /// previous type promotion is removed. @@ -1808,6 +1830,37 @@ return result; } + /// Models the result of joining the flow models [first] and [second] at the + /// merge of two control flow paths. + static FlowModel<Variable, Type> merge<Variable, Type>( + TypeOperations<Variable, Type> typeOperations, + FlowModel<Variable, Type> first, + FlowModel<Variable, Type> second, + Map<Variable, VariableModel<Variable, Type>> emptyVariableMap, + ) { + if (first == null) return second.unsplit(); + if (second == null) return first.unsplit(); + + assert(identical(first.reachable.parent, second.reachable.parent)); + if (first.reachable.locallyReachable && + !second.reachable.locallyReachable) { + return first.unsplit(); + } + if (!first.reachable.locallyReachable && + second.reachable.locallyReachable) { + return second.unsplit(); + } + + Reachability newReachable = + Reachability.join(first.reachable, second.reachable).unsplit(); + Map<Variable, VariableModel<Variable, Type>> newVariableInfo = + FlowModel.joinVariableInfo(typeOperations, first.variableInfo, + second.variableInfo, emptyVariableMap); + + return FlowModel._identicalOrNew( + first, second, newReachable, newVariableInfo); + } + /// Creates a new [FlowModel] object, unless it is equivalent to either /// [first] or [second], in which case one of those objects is re-used. static FlowModel<Variable, Type> _identicalOrNew<Variable, Type>( @@ -2554,9 +2607,17 @@ /// `null` if no `continue` statements have been seen yet. FlowModel<Variable, Type> _continueModel; + /// The reachability checkpoint associated with this loop or switch statement. + /// When analyzing deeply nested `break` and `continue` statements, their flow + /// models need to be unsplit to this point before joining them to the control + /// flow paths for the loop or switch. + final Reachability _checkpoint; + + _BranchTargetContext(this._checkpoint); + @override String toString() => '_BranchTargetContext(breakModel: $_breakModel, ' - 'continueModel: $_continueModel)'; + 'continueModel: $_continueModel, checkpoint: $_checkpoint)'; } /// [_FlowContext] representing a conditional expression. @@ -2710,7 +2771,7 @@ AssignedVariablesNodeInfo<Variable> info = _assignedVariables._getInfoForNode(doStatement); _BranchTargetContext<Variable, Type> context = - new _BranchTargetContext<Variable, Type>(); + new _BranchTargetContext<Variable, Type>(_current.reachable.parent); _stack.add(context); _current = _current.conservativeJoin(info._written, info._captured); _statementToContext[doStatement] = context; @@ -2794,8 +2855,8 @@ ExpressionInfo<Variable, Type> conditionInfo = condition == null ? new ExpressionInfo(_current, _current, _current.setUnreachable()) : _expressionEnd(condition); - _WhileContext<Variable, Type> context = - new _WhileContext<Variable, Type>(conditionInfo); + _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>( + _current.reachable.parent, conditionInfo); _stack.add(context); if (node != null) { _statementToContext[node] = context; @@ -2835,7 +2896,8 @@ _assignedVariables._getInfoForNode(node); _current = _current.conservativeJoin(info._written, info._captured); _SimpleStatementContext<Variable, Type> context = - new _SimpleStatementContext<Variable, Type>(_current); + new _SimpleStatementContext<Variable, Type>( + _current.reachable.parent, _current); _stack.add(context); if (loopVariable != null) { _current = _current.write(loopVariable, writtenType, typeOperations); @@ -2880,7 +2942,8 @@ void handleBreak(Statement target) { _BranchTargetContext<Variable, Type> context = _statementToContext[target]; if (context != null) { - context._breakModel = _join(context._breakModel, _current); + context._breakModel = + _join(context._breakModel, _current.unsplitTo(context._checkpoint)); } _current = _current.setUnreachable(); } @@ -2889,7 +2952,8 @@ void handleContinue(Statement target) { _BranchTargetContext<Variable, Type> context = _statementToContext[target]; if (context != null) { - context._continueModel = _join(context._continueModel, _current); + context._continueModel = _join( + context._continueModel, _current.unsplitTo(context._checkpoint)); } _current = _current.setUnreachable(); } @@ -2924,6 +2988,11 @@ } @override + void ifStatement_conditionBegin() { + _current = _current.split(); + } + + @override void ifStatement_elseBegin() { _IfContext<Variable, Type> context = _stack.last as _IfContext<Variable, Type>; @@ -2944,7 +3013,7 @@ afterThen = _current; // no `else`, so `then` is still current afterElse = context._conditionInfo.ifFalse; } - _current = _join(afterThen, afterElse); + _current = _merge(afterThen, afterElse); } @override @@ -2985,7 +3054,7 @@ @override void labeledStatement_begin(Node node) { _BranchTargetContext<Variable, Type> context = - new _BranchTargetContext<Variable, Type>(); + new _BranchTargetContext<Variable, Type>(_current.reachable.parent); _stack.add(context); _statementToContext[node] = context; } @@ -3140,7 +3209,8 @@ @override void switchStatement_expressionEnd(Statement switchStatement) { _SimpleStatementContext<Variable, Type> context = - new _SimpleStatementContext<Variable, Type>(_current); + new _SimpleStatementContext<Variable, Type>( + _current.reachable.parent, _current); _stack.add(context); _statementToContext[switchStatement] = context; } @@ -3232,8 +3302,8 @@ void whileStatement_bodyBegin( Statement whileStatement, Expression condition) { ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(condition); - _WhileContext<Variable, Type> context = - new _WhileContext<Variable, Type>(conditionInfo); + _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>( + _current.reachable.parent, conditionInfo); _stack.add(context); _statementToContext[whileStatement] = context; _current = conditionInfo.ifTrue; @@ -3302,6 +3372,11 @@ FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) => FlowModel.join(typeOperations, first, second, _current._emptyVariableMap); + FlowModel<Variable, Type> _merge( + FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) => + FlowModel.merge( + typeOperations, first, second, _current._emptyVariableMap); + /// Associates [expression], which should be the most recently visited /// expression, with the given [expressionInfo] object, and updates the /// current flow model state to correspond to it. @@ -3373,11 +3448,13 @@ /// after evaluation of the switch expression. final FlowModel<Variable, Type> _previous; - _SimpleStatementContext(this._previous); + _SimpleStatementContext(Reachability checkpoint, this._previous) + : super(checkpoint); @override String toString() => '_SimpleStatementContext(breakModel: $_breakModel, ' - 'continueModel: $_continueModel, previous: $_previous)'; + 'continueModel: $_continueModel, previous: $_previous, ' + 'checkpoint: $_checkpoint)'; } /// [_FlowContext] representing a try statement. @@ -3430,9 +3507,11 @@ /// Flow models associated with the loop condition. final ExpressionInfo<Variable, Type> _conditionInfo; - _WhileContext(this._conditionInfo); + _WhileContext(Reachability checkpoint, this._conditionInfo) + : super(checkpoint); @override String toString() => '_WhileContext(breakModel: $_breakModel, ' - 'continueModel: $_continueModel, conditionInfo: $_conditionInfo)'; + 'continueModel: $_continueModel, conditionInfo: $_conditionInfo, ' + 'checkpoint: $_checkpoint)'; }
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart index 16ae7ff..0af6a2f 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -179,6 +179,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var varExpr = _Expression(); flow.variableRead(varExpr, x); flow.equalityOp_rightBegin(varExpr, _Type('int?')); @@ -203,6 +204,7 @@ var x = h.addVar('x', 'int'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var varExpr = _Expression(); flow.variableRead(varExpr, x); flow.equalityOp_rightBegin(varExpr, _Type('int')); @@ -227,6 +229,7 @@ test('equalityOp(<expr> == <expr>) has no special effect', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); flow.equalityOp_rightBegin(_Expression(), _Type('int?')); var expr = _Expression(); var successIsReachable = flow.equalityOp_end( @@ -244,6 +247,7 @@ test('equalityOp(<expr> != <expr>) has no special effect', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); flow.equalityOp_rightBegin(_Expression(), _Type('int?')); var expr = _Expression(); var successIsReachable = flow @@ -262,6 +266,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var varExpr = _Expression(); flow.variableRead(varExpr, x); flow.equalityOp_rightBegin(varExpr, _Type('int?')); @@ -281,6 +286,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var varExpr = _Expression(); flow.variableRead(varExpr, x); flow.equalityOp_rightBegin(varExpr, _Type('int?')); @@ -305,6 +311,7 @@ var x = h.addVar('x', 'int'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var varExpr = _Expression(); flow.variableRead(varExpr, x); flow.equalityOp_rightBegin(varExpr, _Type('int')); @@ -331,6 +338,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var nullExpr = _Expression(); flow.nullLiteral(nullExpr); flow.equalityOp_rightBegin(nullExpr, _Type('Null')); @@ -351,6 +359,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var nullExpr = _Expression(); flow.equalityOp_rightBegin(nullExpr, _Type('Null')); var varExpr = _Expression(); @@ -370,6 +379,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var nullExpr = _Expression(); flow.nullLiteral(nullExpr); flow.equalityOp_rightBegin(nullExpr, _Type('Null')); @@ -388,6 +398,7 @@ test('equalityOp(null == null) equivalent to true', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('Null')); var null2 = _Expression(); @@ -406,6 +417,7 @@ test('equalityOp(null != null) equivalent to false', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('Null')); var null2 = _Expression(); @@ -424,6 +436,7 @@ test('equalityOp(null == non-null) is not equivalent to false', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('Null')); var null2 = _Expression(); @@ -443,6 +456,7 @@ test('equalityOp(null != non-null) is not equivalent to true', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('Null')); var null2 = _Expression(); @@ -463,6 +477,7 @@ test('equalityOp(non-null == null) is not equivalent to false', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('int')); var null2 = _Expression(); @@ -483,6 +498,7 @@ test('equalityOp(non-null != null) is not equivalent to true', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var null1 = _Expression(); flow.equalityOp_rightBegin(null1, _Type('int')); var null2 = _Expression(); @@ -599,6 +615,7 @@ var h = _Harness(); var expr = _Expression(); var flow = h.createFlow(); + flow.ifStatement_conditionBegin(); flow.ifStatement_thenBegin(expr); expect(() => flow.finish(), _asserts); }); @@ -1040,6 +1057,94 @@ }); }); + test('handleBreak handles deep nesting', () { + var h = _Harness(); + var whileStatement = _Statement(); + h.assignedVariables((vars) { + vars.nest(whileStatement, () {}); + }); + h.run((flow) { + flow.whileStatement_conditionBegin(whileStatement); + flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)()); + h.if_(h.expr, () { + h.if_(h.expr, () { + flow.handleBreak(whileStatement); + }); + }); + flow.handleExit(); + expect(flow.isReachable, false); + flow.whileStatement_end(); + expect(flow.isReachable, true); + }); + }); + + test('handleBreak handles mixed nesting', () { + var h = _Harness(); + var whileStatement = _Statement(); + h.assignedVariables((vars) { + vars.nest(whileStatement, () {}); + }); + h.run((flow) { + flow.whileStatement_conditionBegin(whileStatement); + flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)()); + h.if_(h.expr, () { + h.if_(h.expr, () { + flow.handleBreak(whileStatement); + }); + flow.handleBreak(whileStatement); + }); + flow.handleBreak(whileStatement); + expect(flow.isReachable, false); + flow.whileStatement_end(); + expect(flow.isReachable, true); + }); + }); + + test('handleContinue handles deep nesting', () { + var h = _Harness(); + var doStatement = _Statement(); + h.assignedVariables((vars) { + vars.nest(doStatement, () {}); + }); + h.run((flow) { + flow.doStatement_bodyBegin(doStatement); + h.if_(h.expr, () { + h.if_(h.expr, () { + flow.handleContinue(doStatement); + }); + }); + flow.handleExit(); + expect(flow.isReachable, false); + flow.doStatement_conditionBegin(); + expect(flow.isReachable, true); + flow.doStatement_end(h.booleanLiteral(true)()); + expect(flow.isReachable, false); + }); + }); + + test('handleContinue handles mixed nesting', () { + var h = _Harness(); + var doStatement = _Statement(); + h.assignedVariables((vars) { + vars.nest(doStatement, () {}); + }); + h.run((flow) { + flow.doStatement_bodyBegin(doStatement); + h.if_(h.expr, () { + h.if_(h.expr, () { + flow.handleContinue(doStatement); + }); + flow.handleContinue(doStatement); + }); + flow.handleContinue(doStatement); + expect(flow.isReachable, false); + flow.doStatement_conditionBegin(); + expect(flow.isReachable, true); + flow.doStatement_end(h.booleanLiteral(true)()); + expect(flow.isReachable, false); + }); + }); + test('ifNullExpression allows ensure guarding', () { var h = _Harness(); var x = h.addVar('x', 'int?'); @@ -1119,11 +1224,28 @@ }); }); + test('ifStatement with early exit promotes in unreachable code', () { + var h = _Harness(); + var x = h.addVar('x', 'int?'); + h.run((flow) { + h.declare(x, initialized: true); + flow.handleExit(); + expect(flow.isReachable, false); + flow.ifStatement_conditionBegin(); + flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))()); + flow.handleExit(); + flow.ifStatement_end(false); + expect(flow.isReachable, false); + expect(flow.promotedType(x).type, 'int'); + }); + }); + test('ifStatement_end(false) keeps else branch if then branch exits', () { var h = _Harness(); var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))()); flow.handleExit(); flow.ifStatement_end(false); @@ -1138,6 +1260,7 @@ var x = h.addVar('x', declaredType); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); var read = _Expression(); flow.variableRead(read, x); var expr = _Expression(); @@ -1190,6 +1313,7 @@ test('isExpression_end does nothing if applied to a non-variable', () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var subExpr = _Expression(); var expr = _Expression(); var failureReachable = @@ -1207,6 +1331,7 @@ () { var h = _Harness(); h.run((flow) { + flow.ifStatement_conditionBegin(); var subExpr = _Expression(); var expr = _Expression(); var failureReachable = @@ -1305,6 +1430,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: true); var wholeExpr = _Expression(); flow.logicalBinaryOp_end(wholeExpr, h.notNull(x, _Type('int?'))(), @@ -1321,6 +1447,7 @@ var x = h.addVar('x', 'int?'); h.run((flow) { h.declare(x, initialized: true); + flow.ifStatement_conditionBegin(); flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: false); var wholeExpr = _Expression(); flow.logicalBinaryOp_end(wholeExpr, h.eqNull(x, _Type('int?'))(), @@ -2163,6 +2290,7 @@ h.promote(x, 'int'); expect(flow.promotedType(x).type, 'int'); // if (false) { + flow.ifStatement_conditionBegin(); var falseExpression = _Expression(); flow.booleanLiteral(falseExpression, false); flow.ifStatement_thenBegin(falseExpression); @@ -2328,6 +2456,55 @@ expect(s2.reachable, same(Reachability.initial)); }); + group('unsplitTo', () { + test('no change', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var result = s1.unsplitTo(s1.reachable.parent); + expect(result, same(s1)); + }); + + test('unsplit once, reachable', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var s2 = s1.split(); + var result = s2.unsplitTo(s1.reachable.parent); + expect(result.reachable, same(s1.reachable)); + }); + + test('unsplit once, unreachable', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var s2 = s1.split().setUnreachable(); + var result = s2.unsplitTo(s1.reachable.parent); + expect(result.reachable.locallyReachable, false); + expect(result.reachable.parent, same(s1.reachable.parent)); + }); + + test('unsplit twice, reachable', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var s2 = s1.split(); + var s3 = s2.split(); + var result = s3.unsplitTo(s1.reachable.parent); + expect(result.reachable, same(s1.reachable)); + }); + + test('unsplit twice, top unreachable', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var s2 = s1.split(); + var s3 = s2.split().setUnreachable(); + var result = s3.unsplitTo(s1.reachable.parent); + expect(result.reachable.locallyReachable, false); + expect(result.reachable.parent, same(s1.reachable.parent)); + }); + + test('unsplit twice, previous unreachable', () { + var s1 = FlowModel<_Var, _Type>(Reachability.initial.split()); + var s2 = s1.split().setUnreachable(); + var s3 = s2.split(); + var result = s3.unsplitTo(s1.reachable.parent); + expect(result.reachable.locallyReachable, false); + expect(result.reachable.parent, same(s1.reachable.parent)); + }); + }); + group('tryPromoteForTypeCheck', () { test('unpromoted -> unchanged (same)', () { var h = _Harness(); @@ -3581,6 +3758,101 @@ }); }); }); + + group('merge', () { + var x = _Var('x', _Type('Object?')); + var intType = _Type('int'); + var stringType = _Type('String'); + const emptyMap = const <_Var, VariableModel<_Var, _Type>>{}; + + VariableModel<_Var, _Type> varModel(List<_Type> promotionChain, + {bool assigned = false}) => + VariableModel<_Var, _Type>( + promotionChain, + promotionChain ?? [], + assigned, + !assigned, + false, + ); + + test('first is null', () { + var h = _Harness(); + var s1 = FlowModel.withInfo(Reachability.initial.split(), {}); + var result = FlowModel.merge(h, null, s1, emptyMap); + expect(result.reachable, same(Reachability.initial)); + }); + + test('second is null', () { + var h = _Harness(); + var splitPoint = Reachability.initial.split(); + var afterSplit = splitPoint.split(); + var s1 = FlowModel.withInfo(afterSplit, {}); + var result = FlowModel.merge(h, s1, null, emptyMap); + expect(result.reachable, same(splitPoint)); + }); + + test('both are reachable', () { + var h = _Harness(); + var splitPoint = Reachability.initial.split(); + var afterSplit = splitPoint.split(); + var s1 = FlowModel.withInfo(afterSplit, { + x: varModel([intType]) + }); + var s2 = FlowModel.withInfo(afterSplit, { + x: varModel([stringType]) + }); + var result = FlowModel.merge(h, s1, s2, emptyMap); + expect(result.reachable, same(splitPoint)); + expect(result.variableInfo[x].promotedTypes, isNull); + }); + + test('first is unreachable', () { + var h = _Harness(); + var splitPoint = Reachability.initial.split(); + var afterSplit = splitPoint.split(); + var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), { + x: varModel([intType]) + }); + var s2 = FlowModel.withInfo(afterSplit, { + x: varModel([stringType]) + }); + var result = FlowModel.merge(h, s1, s2, emptyMap); + expect(result.reachable, same(splitPoint)); + expect(result.variableInfo, same(s2.variableInfo)); + }); + + test('second is unreachable', () { + var h = _Harness(); + var splitPoint = Reachability.initial.split(); + var afterSplit = splitPoint.split(); + var s1 = FlowModel.withInfo(afterSplit, { + x: varModel([intType]) + }); + var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), { + x: varModel([stringType]) + }); + var result = FlowModel.merge(h, s1, s2, emptyMap); + expect(result.reachable, same(splitPoint)); + expect(result.variableInfo, same(s1.variableInfo)); + }); + + test('both are unreachable', () { + var h = _Harness(); + var splitPoint = Reachability.initial.split(); + var afterSplit = splitPoint.split(); + var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), { + x: varModel([intType]) + }); + var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), { + x: varModel([stringType]) + }); + var result = FlowModel.merge(h, s1, s2, emptyMap); + expect(result.reachable.locallyReachable, false); + expect(result.reachable.parent, same(splitPoint.parent)); + expect(result.variableInfo[x].promotedTypes, isNull); + }); + }); + group('inheritTested', () { var x = _Var('x', _Type('Object?')); var intType = _Type('int'); @@ -3881,6 +4153,12 @@ callback(_AssignedVariablesHarness(_assignedVariables)); } + LazyExpression booleanLiteral(bool value) => () { + var expr = _Expression(); + _flow.booleanLiteral(expr, value); + return expr; + }; + @override TypeClassification classifyType(_Type type) { if (isSubtypeOf(type, _Type('Object'))) { @@ -3943,6 +4221,7 @@ /// Invokes flow analysis of an `if` statement with no `else` part. void if_(LazyExpression cond, void ifTrue()) { + _flow.ifStatement_conditionBegin(); _flow.ifStatement_thenBegin(cond()); ifTrue(); _flow.ifStatement_end(false); @@ -3950,6 +4229,7 @@ /// Invokes flow analysis of an `if` statement with an `else` part. void ifElse(LazyExpression cond, void ifTrue(), void ifFalse()) { + _flow.ifStatement_conditionBegin(); _flow.ifStatement_thenBegin(cond()); ifTrue(); _flow.ifStatement_elseBegin();
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart new file mode 100644 index 0000000..4a3c9a5 --- /dev/null +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart
@@ -0,0 +1,31 @@ +// Copyright (c) 2020, 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. + +// These tests verify that the kinds of constructs we expect to cause type +// promotion continue to function properly even when used inside unreachable +// code. + +ifIsNot(Object o) { + return; + if (o is! int) return; + /*int*/ o; +} + +ifIsNot_listElement(Object o) { + return; + [if (o is! int) throw 'x']; + /*int*/ o; +} + +ifIsNot_setElement(Object o) { + return; + ({if (o is! int) throw 'x'}); + /*int*/ o; +} + +ifIsNot_mapElement(Object o) { + return; + ({if (o is! int) 0: throw 'x'}); + /*int*/ o; +}
diff --git a/pkg/analysis_server/analysis_options.yaml b/pkg/analysis_server/analysis_options.yaml index 5995e86..31a937b 100644 --- a/pkg/analysis_server/analysis_options.yaml +++ b/pkg/analysis_server/analysis_options.yaml
@@ -1,4 +1,4 @@ -include: package:pedantic/analysis_options.1.8.0.yaml +include: package:pedantic/analysis_options.1.9.0.yaml analyzer: # This currently finds ~1,200 implicit-casts issues when enabled. @@ -16,25 +16,11 @@ linter: rules: - await_only_futures + - avoid_single_cascade_in_expression_statements - empty_statements + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - prefer_initializing_formals - unnecessary_brace_in_string_interps - # - # Delta from pedantic 1.8.0 to 1.9.0 - # - - always_declare_return_types - - always_require_non_null_named_parameters - - annotate_overrides - - avoid_null_checks_in_equality_operators - - camel_case_extensions - - omit_local_variable_types - - prefer_adjacent_string_concatenation - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_final_fields - - prefer_for_elements_to_map_fromIterable - - prefer_generic_function_type_aliases - - prefer_if_null_operators - - prefer_single_quotes - - prefer_spread_collections - - unnecessary_this - - use_function_type_syntax_for_parameters + - unnecessary_overrides + - void_checks
diff --git a/pkg/analysis_server/lib/protocol/protocol.dart b/pkg/analysis_server/lib/protocol/protocol.dart index 53b008f..f995858 100644 --- a/pkg/analysis_server/lib/protocol/protocol.dart +++ b/pkg/analysis_server/lib/protocol/protocol.dart
@@ -305,7 +305,7 @@ /// with the given [id]. If [_result] is provided, it will be used as the /// result; otherwise an empty result will be used. If an [error] is provided /// then the response will represent an error condition. - Response(this.id, {Map<String, Object> result, this.error}) : result = result; + Response(this.id, {this.result, this.error}); /// Create and return the `DEBUG_PORT_COULD_NOT_BE_OPENED` error response. Response.debugPortCouldNotBeOpened(Request request, dynamic error)
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart index 0a4dbf9..c16084d 100644 --- a/pkg/analysis_server/lib/src/context_manager.dart +++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -108,10 +108,9 @@ /// added to the context. Map<String, Source> sources = HashMap<String, Source>(); - ContextInfo(ContextManagerImpl contextManager, this.parent, Folder folder, + ContextInfo(ContextManagerImpl contextManager, this.parent, this.folder, File packagespecFile, this.disposition) - : folder = folder, - pathFilter = PathFilter( + : pathFilter = PathFilter( folder.path, null, contextManager.resourceProvider.pathContext) { packageDescriptionPath = packagespecFile.path; parent.children.add(this);
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart index 93b2db4..9dd70bf 100644 --- a/pkg/analysis_server/lib/src/search/search_domain.dart +++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -25,9 +25,7 @@ /// Initialize a newly created handler to handle requests for the given /// [server]. - SearchDomainHandler(AnalysisServer server) - : server = server, - searchEngine = server.searchEngine; + SearchDomainHandler(this.server) : searchEngine = server.searchEngine; Future findElementReferences(protocol.Request request) async { var params =
diff --git a/pkg/analysis_server/lib/src/services/completion/completion_core.dart b/pkg/analysis_server/lib/src/services/completion/completion_core.dart index 5339b4d..a84b968 100644 --- a/pkg/analysis_server/lib/src/services/completion/completion_core.dart +++ b/pkg/analysis_server/lib/src/services/completion/completion_core.dart
@@ -40,9 +40,8 @@ /// Initialize a newly created completion request based on the given /// arguments. CompletionRequestImpl( - this.result, int offset, this.useNewRelevance, this.performance) - : offset = offset, - replacementOffset = offset, + this.result, this.offset, this.useNewRelevance, this.performance) + : replacementOffset = offset, replacementLength = 0; @override
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart index 9bbc308..df843b8 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
@@ -451,7 +451,7 @@ @override void visitExtendsClause(ExtendsClause node) { inExtendsClause = true; - return super.visitExtendsClause(node); + super.visitExtendsClause(node); } /// Return `true` if the [identifier] is composed of one or more underscore
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index e506f30..d04d443 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -253,7 +253,7 @@ List.from(fixList[0].change.linkedEditGroups); for (var i = 1; i < fixList.length; i++) { edits.addAll(fixList[i].change.edits[0].edits); - sourceChange.linkedEditGroups..addAll(fixList[i].change.linkedEditGroups); + sourceChange.linkedEditGroups.addAll(fixList[i].change.linkedEditGroups); } // Sort the list of SourceEdits so that when the edits are applied, they // are applied from the end of the file to the top of the file.
diff --git a/pkg/analysis_server/lib/src/services/correction/sort_members.dart b/pkg/analysis_server/lib/src/services/correction/sort_members.dart index 34e0ba4..cf3db41 100644 --- a/pkg/analysis_server/lib/src/services/correction/sort_members.dart +++ b/pkg/analysis_server/lib/src/services/correction/sort_members.dart
@@ -262,10 +262,8 @@ final int end; final String text; - _MemberInfo(this.item, this.name, int offset, int length, this.text) - : offset = offset, - length = length, - end = offset + length; + _MemberInfo(this.item, this.name, this.offset, this.length, this.text) + : end = offset + length; @override String toString() {
diff --git a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart index 398620c..99e8dd2 100644 --- a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart +++ b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
@@ -1083,7 +1083,7 @@ _enclosingVName = _vNameFromElement(_enclosingElement, schema.FUNCTION_KIND); } - return f(); + f(); } finally { _enclosingElement = outerEnclosingElement; _enclosingClassElement = outerEnclosingClassElement;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart index 7cfe3fe..7f96d3a 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
@@ -148,10 +148,9 @@ elementKind = ElementKind.METHOD; _ClassMemberValidator.forRename( - this.searchEngine, this.sessionHelper, Element element, this.name) + this.searchEngine, this.sessionHelper, this.element, this.name) : isRename = true, library = element.library, - element = element, elementClass = element.enclosingElement, elementKind = element.kind;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart index 3a4a3a5..6187abe 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
@@ -104,10 +104,9 @@ final List<SearchMatch> references = <SearchMatch>[]; _ExtensionMemberValidator.forRename( - this.searchEngine, this.sessionHelper, Element element, this.name) + this.searchEngine, this.sessionHelper, this.element, this.name) : isRename = true, library = element.library, - element = element, elementExtension = element.enclosingElement, elementKind = element.kind;
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml index 842ac85..da3e6a6 100644 --- a/pkg/analysis_server/pubspec.yaml +++ b/pkg/analysis_server/pubspec.yaml
@@ -35,5 +35,5 @@ logging: any matcher: any mockito: any - pedantic: ^1.8.0 + pedantic: ^1.9.0 test_reflective_loader: any
diff --git a/pkg/analysis_server/test/integration/support/integration_tests.dart b/pkg/analysis_server/test/integration/support/integration_tests.dart index e51eace..f58ca4d 100644 --- a/pkg/analysis_server/test/integration/support/integration_tests.dart +++ b/pkg/analysis_server/test/integration/support/integration_tests.dart
@@ -690,9 +690,7 @@ /// Iterable matcher which we use to test the contents of the list. final Matcher iterableMatcher; - _ListOf(elementMatcher) - : elementMatcher = elementMatcher, - iterableMatcher = everyElement(elementMatcher); + _ListOf(this.elementMatcher) : iterableMatcher = everyElement(elementMatcher); @override Description describe(Description description) =>
diff --git a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart index b911615..a9f9ce5 100644 --- a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart +++ b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
@@ -365,7 +365,7 @@ ..writeIndentedln('switch (obj) {') ..indent(); consts.forEach((cons) { - buffer..writeIndentedln('case ${cons.valueAsLiteral}:'); + buffer.writeIndentedln('case ${cons.valueAsLiteral}:'); }); buffer ..indent() @@ -380,9 +380,8 @@ ..writeIndentedln('}'); namespace.members.whereType<Const>().forEach((cons) { _writeDocCommentsAndAnnotations(buffer, cons); - buffer - ..writeIndentedln( - 'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});'); + buffer.writeIndentedln( + 'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});'); }); buffer ..writeln() @@ -662,7 +661,7 @@ final nullOp = shouldBeOmittedIfNoValue ? '' : '?'; final valueCode = _isSpecType(field.type) ? '${field.name}$nullOp.toJson()' : field.name; - buffer..writeIndented('''$mapName['${field.name}'] = $valueCode'''); + buffer.writeIndented('''$mapName['${field.name}'] = $valueCode'''); if (!field.allowsUndefined && !field.allowsNull) { buffer.write(''' ?? (throw '${field.name} is required but was not set')'''); } @@ -792,7 +791,7 @@ buffer..write(' && (')..write('$valueCode.keys.every((item) => '); _writeTypeCheckCondition( buffer, interface, 'item', type.indexType, reporter); - buffer..write('&& $valueCode.values.every((item) => '); + buffer.write('&& $valueCode.values.every((item) => '); _writeTypeCheckCondition( buffer, interface, 'item', type.valueType, reporter); buffer.write(')))');
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index bc2bbad..0d46f4a 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1498,6 +1498,7 @@ @override void visitIfElement(IfElement node) { + _flowAnalysis?.flow?.ifStatement_conditionBegin(); Expression condition = node.condition; InferenceContext.setType(condition, typeProvider.boolType); // TODO(scheglov) Do we need these checks for null? @@ -1535,6 +1536,7 @@ @override void visitIfStatement(IfStatement node) { checkUnreachableNode(node); + _flowAnalysis?.flow?.ifStatement_conditionBegin(); Expression condition = node.condition;
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart index 3f0e79b..7288dd8 100644 --- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart +++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -1184,6 +1184,7 @@ @override StatementInferenceResult visitIfStatement(IfStatement node) { + inferrer.flowAnalysis.ifStatement_conditionBegin(); InterfaceType expectedType = inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); ExpressionInferenceResult conditionResult = inferrer.inferExpression( @@ -1411,6 +1412,7 @@ element.elementType = spreadElementType ?? const DynamicType(); return new ExpressionInferenceResult(element.elementType, replacement); } else if (element is IfElement) { + inferrer.flowAnalysis.ifStatement_conditionBegin(); DartType boolType = inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); ExpressionInferenceResult conditionResult = inferrer.inferExpression( @@ -1914,6 +1916,7 @@ return replacement; } else if (entry is IfMapEntry) { + inferrer.flowAnalysis.ifStatement_conditionBegin(); DartType boolType = inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); ExpressionInferenceResult conditionResult = inferrer.inferExpression(
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 index af42f14..2ae32bf 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
@@ -377,9 +377,7 @@ factMap[variable], isCheck.functionNestingLevel, isCheck.checkedType, - [] - ..addAll(isCheck._blockingScopes) - ..add(_currentScope)); + [...isCheck._blockingScopes, _currentScope]); factMap[variable] = facts; _factCacheState = facts; }
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index b370368..47cfd38 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -2783,6 +2783,7 @@ speed speedup split +splits splitter spread spreadable
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart index 7363e60..7d4a32e 100644 --- a/pkg/nnbd_migration/lib/src/edge_builder.dart +++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -947,6 +947,7 @@ @override DecoratedType visitIfStatement(IfStatement node) { + _flowAnalysis.ifStatement_conditionBegin(); _checkExpressionNotNull(node.condition); NullabilityNode trueGuard; NullabilityNode falseGuard;
diff --git a/runtime/tests/vm/dart/regress_43682_test.dart b/runtime/tests/vm/dart/regress_43682_test.dart new file mode 100644 index 0000000..63ef705 --- /dev/null +++ b/runtime/tests/vm/dart/regress_43682_test.dart
@@ -0,0 +1,32 @@ +// Copyright (c) 2020, 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. + +// VMOptions=--deterministic --optimization_counter_threshold=20 + +// Verifies that SSA construction doesn't crash when handling a Phi +// corresponding to an expression temp in case of OSR with non-empty +// expression stack. +// Regression test for https://github.com/dart-lang/sdk/issues/43682. + +import 'package:expect/expect.dart'; + +class Foo { + List<Object> data; + Foo(this.data); +} + +Map<String, Foo> foo(List<Object> objects) { + Map<String, Foo> map = {}; + // OSR happens during '...objects' spread, and Foo instance is already + // allocated and remains on the stack during OSR. + // OSR Phi corresponding to that value is stored into 'foo' local and + // then loaded from it, but it doesn't correspond to 'foo' environment slot. + final foo = new Foo([...objects]); + map['hi'] = foo; + return map; +} + +main() { + Expect.equals(30, foo(List.filled(30, Object()))['hi']!.data.length); +}
diff --git a/runtime/tests/vm/dart_2/regress_43682_test.dart b/runtime/tests/vm/dart_2/regress_43682_test.dart new file mode 100644 index 0000000..15924b1 --- /dev/null +++ b/runtime/tests/vm/dart_2/regress_43682_test.dart
@@ -0,0 +1,32 @@ +// Copyright (c) 2020, 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. + +// VMOptions=--deterministic --optimization_counter_threshold=20 + +// Verifies that SSA construction doesn't crash when handling a Phi +// corresponding to an expression temp in case of OSR with non-empty +// expression stack. +// Regression test for https://github.com/dart-lang/sdk/issues/43682. + +import 'package:expect/expect.dart'; + +class Foo { + List<Object> data; + Foo(this.data); +} + +Map<String, Foo> foo(List<Object> objects) { + Map<String, Foo> map = {}; + // OSR happens during '...objects' spread, and Foo instance is already + // allocated and remains on the stack during OSR. + // OSR Phi corresponding to that value is stored into 'foo' local and + // then loaded from it, but it doesn't correspond to 'foo' environment slot. + final foo = new Foo([...objects]); + map['hi'] = foo; + return map; +} + +main() { + Expect.equals(30, foo(List.filled(30, Object()))['hi'].data.length); +}
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc index 9196b2d..82430b4 100644 --- a/runtime/vm/compiler/backend/flow_graph.cc +++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -1453,13 +1453,23 @@ // there as incoming value by renaming or it was stored there by // StoreLocal which took this Phi from another local via LoadLocal, // to which this reasoning applies recursively. + // // This means that we are guaranteed to process LoadLocal for a - // matching variable first. + // matching variable first, unless there was an OSR with a non-empty + // expression stack. In the latter case, Phi inserted by + // FlowGraph::AddSyntheticPhis for expression temp will not have an + // assigned type and may be accessed by StoreLocal and subsequent + // LoadLocal. + // if (!phi->HasType()) { - ASSERT((index < phi->block()->phis()->length()) && - ((*phi->block()->phis())[index] == phi)); - phi->UpdateType( - CompileType::FromAbstractType(load->local().type())); + // Check if phi corresponds to the same slot. + auto* phis = phi->block()->phis(); + if ((index < phis->length()) && (*phis)[index] == phi) { + phi->UpdateType( + CompileType::FromAbstractType(load->local().type())); + } else { + ASSERT(IsCompiledForOsr() && (phi->block()->stack_depth() > 0)); + } } } break;
diff --git a/runtime/vm/flags.cc b/runtime/vm/flags.cc index ee2708f..901856c 100644 --- a/runtime/vm/flags.cc +++ b/runtime/vm/flags.cc
@@ -95,11 +95,13 @@ Flag(const char* name, const char* comment, FlagHandler handler) : name_(name), comment_(comment), + string_value_("false"), flag_handler_(handler), type_(kFlagHandler) {} Flag(const char* name, const char* comment, OptionHandler handler) : name_(name), comment_(comment), + string_value_(nullptr), option_handler_(handler), type_(kOptionHandler) {} @@ -148,6 +150,7 @@ const char* name_; const char* comment_; + const char* string_value_; union { void* addr_; bool* bool_ptr_; @@ -332,9 +335,11 @@ } else { return false; } + flag->string_value_ = argument; break; } case Flag::kOptionHandler: { + flag->string_value_ = argument; (flag->option_handler_)(argument); break; } @@ -487,8 +492,7 @@ #ifndef PRODUCT void Flags::PrintFlagToJSONArray(JSONArray* jsarr, const Flag* flag) { - if (flag->IsUnrecognized() || flag->type_ == Flag::kFlagHandler || - flag->type_ == Flag::kOptionHandler) { + if (flag->IsUnrecognized()) { return; } JSONObject jsflag(jsarr); @@ -521,6 +525,20 @@ } break; } + case Flag::kFlagHandler: { + jsflag.AddProperty("_flagType", "Bool"); + jsflag.AddProperty("valueAsString", flag->string_value_); + break; + } + case Flag::kOptionHandler: { + jsflag.AddProperty("_flagType", "String"); + if (flag->string_value_ != nullptr) { + jsflag.AddProperty("valueAsString", flag->string_value_); + } else { + // valueAsString missing means NULL. + } + break; + } default: UNREACHABLE(); break;
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart index 8d2a625aa..150a759 100644 --- a/sdk/lib/async/stream.dart +++ b/sdk/lib/async/stream.dart
@@ -483,11 +483,13 @@ * On errors from this stream, the [onError] handler is called with the * error object and possibly a stack trace. * - * The [onError] callback must be of type `void onError(Object error)` or - * `void onError(Object error, StackTrace stackTrace)`. If [onError] accepts - * two arguments it is called with the error object and the stack trace - * (which could be `null` if this stream itself received an error without - * stack trace). + * The [onError] callback must be of type `void Function(Object error)` or + * `void Function(Object error, StackTrace)`. + * The function type determines whether [onError] is invoked with a stack + * trace argument. + * The stack trace argument may be [StackTrace.empty] if this stream received + * an error without a stack trace. + * * Otherwise it is called with just the error object. * If [onError] is omitted, any errors on this stream are considered unhandled, * and will be passed to the current [Zone]'s error handler.
diff --git a/tools/VERSION b/tools/VERSION index e82a042..7d7e434 100644 --- a/tools/VERSION +++ b/tools/VERSION
@@ -27,5 +27,5 @@ MAJOR 2 MINOR 11 PATCH 0 -PRERELEASE 196 +PRERELEASE 197 PRERELEASE_PATCH 0 \ No newline at end of file