AST. Implement IfNull. Bug: https://github.com/dart-lang/sdk/issues/63882 Change-Id: Ibd86e9024f2f0ec7cfd45246459284193a70f61e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/529660 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/analyzer/api.txt b/pkg/analyzer/api.txt index 893dd3f..657c714 100644 --- a/pkg/analyzer/api.txt +++ b/pkg/analyzer/api.txt
@@ -400,6 +400,7 @@ addGuardedPattern (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) addHideCombinator (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) addIfElement (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) + addIfNull (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>), experimental) addIfStatement (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) addImplementsClause (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) addImplicitCallReference (method: void Function(AbstractAnalysisRule, AstVisitor2<dynamic>)) @@ -1138,6 +1139,7 @@ visitGuardedPattern (method: R? Function(GuardedPattern)) visitHideCombinator (method: R? Function(HideCombinator)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference)) @@ -1749,6 +1751,10 @@ rightParenthesis (getter: Token) thenElement (getter: CollectionElement) thenElement2 (getter: CollectionElement, experimental) + IfNull (class extends Object implements Expression, abstract, final, experimental): + leftOperand (getter: Expression) + operator (getter: Token) + rightOperand (getter: Expression) IfStatement (class extends Object implements Statement, abstract, final): caseClause (getter: CaseClause?) elseKeyword (getter: Token?) @@ -3035,6 +3041,7 @@ visitHideCombinator (method: R? Function(HideCombinator)) visitIdentifier (method: R? Function(Identifier)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference)) @@ -3426,6 +3433,7 @@ visitGuardedPattern (method: R? Function(GuardedPattern)) visitHideCombinator (method: R? Function(HideCombinator)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference)) @@ -3802,6 +3810,7 @@ visitGuardedPattern (method: R? Function(GuardedPattern)) visitHideCombinator (method: R? Function(HideCombinator)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference)) @@ -4178,6 +4187,7 @@ visitGuardedPattern (method: R? Function(GuardedPattern)) visitHideCombinator (method: R? Function(HideCombinator)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference)) @@ -4556,6 +4566,7 @@ visitGuardedPattern (method: T? Function(GuardedPattern)) visitHideCombinator (method: T? Function(HideCombinator)) visitIfElement (method: T? Function(IfElement)) + visitIfNull (method: T? Function(IfNull), experimental) visitIfStatement (method: T? Function(IfStatement)) visitImplementsClause (method: T? Function(ImplementsClause)) visitImplicitCallReference (method: T? Function(ImplicitCallReference)) @@ -4933,6 +4944,7 @@ visitGuardedPattern (method: R? Function(GuardedPattern)) visitHideCombinator (method: R? Function(HideCombinator)) visitIfElement (method: R? Function(IfElement)) + visitIfNull (method: R? Function(IfNull), experimental) visitIfStatement (method: R? Function(IfStatement)) visitImplementsClause (method: R? Function(ImplementsClause)) visitImplicitCallReference (method: R? Function(ImplicitCallReference))
diff --git a/pkg/analyzer/lib/analysis_rule/rule_visitor_registry.g.dart b/pkg/analyzer/lib/analysis_rule/rule_visitor_registry.g.dart index 42ede3b..aae7255 100644 --- a/pkg/analyzer/lib/analysis_rule/rule_visitor_registry.g.dart +++ b/pkg/analyzer/lib/analysis_rule/rule_visitor_registry.g.dart
@@ -744,6 +744,9 @@ void addIfElement(AbstractAnalysisRule rule, AstVisitor2 visitor); + @experimental + void addIfNull(AbstractAnalysisRule rule, AstVisitor2 visitor); + void addIfStatement(AbstractAnalysisRule rule, AstVisitor2 visitor); void addImplementsClause(AbstractAnalysisRule rule, AstVisitor2 visitor);
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart index 08c55e8..ed2c783 100644 --- a/pkg/analyzer/lib/dart/ast/ast.dart +++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -157,6 +157,7 @@ HideCombinator, Identifier, IfElement, + IfNull, IfStatement, ImplementsClause, ImplicitCallReference,
diff --git a/pkg/analyzer/lib/dart/ast/visitor.g.dart b/pkg/analyzer/lib/dart/ast/visitor.g.dart index 75d5d75..e9a3816 100644 --- a/pkg/analyzer/lib/dart/ast/visitor.g.dart +++ b/pkg/analyzer/lib/dart/ast/visitor.g.dart
@@ -1122,6 +1122,10 @@ @override R? visitIfElement(IfElement node) => visitNode(node); + @experimental + @override + R? visitIfNull(IfNull node) => visitExpression(node); + @override R? visitIfStatement(IfStatement node) => visitStatement(node); @@ -3188,6 +3192,13 @@ return null; } + @experimental + @override + R? visitIfNull(IfNull node) { + node.visitChildren2(this); + return null; + } + @override R? visitIfStatement(IfStatement node) { node.visitChildren2(this); @@ -4679,6 +4690,10 @@ @override R? visitIfElement(IfElement node) => null; + @experimental + @override + R? visitIfNull(IfNull node) => null; + @override R? visitIfStatement(IfStatement node) => null; @@ -5900,6 +5915,10 @@ @override R? visitIfElement(IfElement node) => _throw(node); + @experimental + @override + R? visitIfNull(IfNull node) => _throw(node); + @override R? visitIfStatement(IfStatement node) => _throw(node); @@ -8458,6 +8477,15 @@ return result; } + @experimental + @override + T? visitIfNull(IfNull node) { + stopwatch.start(); + T? result = _baseVisitor.visitIfNull(node); + stopwatch.stop(); + return result; + } + @override T? visitIfStatement(IfStatement node) { stopwatch.start(); @@ -10212,6 +10240,10 @@ @override R? visitIfElement(IfElement node) => visitNode(node); + @experimental + @override + R? visitIfNull(IfNull node) => visitNode(node); + @override R? visitIfStatement(IfStatement node) => visitNode(node);
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index 7729727..383dd76 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -109,7 +109,7 @@ // TODO(scheglov): Clean up the list of implicitly analyzed files. class AnalysisDriver { /// The version of data format, should be incremented on every format change. - static const int DATA_VERSION = 655; + static const int DATA_VERSION = 656; /// The number of exception contexts allowed to write. Once this field is /// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index cb064d8..fbec9a1 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -3834,7 +3834,8 @@ } } -/// The V1 compatibility projection of a [LogicalAnd] or [LogicalOr]. +/// The V1 compatibility projection of an [IfNull], [LogicalAnd], or +/// [LogicalOr]. final class BinaryExpressionV1Impl extends ExpressionImpl implements BinaryExpression { final ExpressionImpl _origin; @@ -3843,6 +3844,10 @@ _attachV1Children(); } + BinaryExpressionV1Impl._ifNull(IfNullImpl origin) : _origin = origin { + _attachV1Children(); + } + BinaryExpressionV1Impl._or(LogicalOrImpl origin) : _origin = origin { _attachV1Children(); } @@ -3869,16 +3874,18 @@ @experimental @override ExpressionImpl get leftOperand2 => switch (_origin) { + IfNullImpl(:var leftOperand) => leftOperand, LogicalAndImpl(:var leftOperand) => leftOperand, LogicalOrImpl(:var leftOperand) => leftOperand, - _ => throw StateError('Unexpected logical expression: $_origin'), + _ => throw StateError('Unexpected binary expression: $_origin'), }; @override Token get operator => switch (_origin) { + IfNullImpl(:var operator) => operator, LogicalAndImpl(:var operator) => operator, LogicalOrImpl(:var operator) => operator, - _ => throw StateError('Unexpected logical expression: $_origin'), + _ => throw StateError('Unexpected binary expression: $_origin'), }; @override @@ -3890,9 +3897,10 @@ @experimental @override ExpressionImpl get rightOperand2 => switch (_origin) { + IfNullImpl(:var rightOperand) => rightOperand, LogicalAndImpl(:var rightOperand) => rightOperand, LogicalOrImpl(:var rightOperand) => rightOperand, - _ => throw StateError('Unexpected logical expression: $_origin'), + _ => throw StateError('Unexpected binary expression: $_origin'), }; @override @@ -23474,6 +23482,217 @@ E get ifTrue2; } +/// An if-null expression. +@experimental +@AnalyzerPublicApi(message: 'exported by lib/dart/ast/ast.dart') +abstract final class IfNull implements Expression { + /// The expression evaluated first. + Expression get leftOperand; + + /// The `??` operator. + Token get operator; + + /// The expression evaluated if the left operand evaluates to `null`. + Expression get rightOperand; +} + +@GenerateNodeImpl( + api: AstNodeApi.v2, + childEntitiesOrder: [ + GenerateNodeProperty('leftOperand', isInValueExpressionSlot: true), + GenerateNodeProperty('operator'), + GenerateNodeProperty('rightOperand', isInValueExpressionSlot: true), + ], +) +final class IfNullImpl extends ExpressionImpl implements IfNull { + @generated + ExpressionImpl _leftOperand; + + @generated + @override + final Token operator; + + @generated + ExpressionImpl _rightOperand; + + BinaryExpressionV1Impl? _binaryExpression; + + @generated + IfNullImpl({ + required ExpressionImpl leftOperand, + required this.operator, + required ExpressionImpl rightOperand, + }) : _leftOperand = leftOperand, + _rightOperand = rightOperand { + _becomeParentOf2(leftOperand); + _becomeParentOf2(rightOperand); + } + + @generated + @override + Token get beginToken { + return leftOperand.beginToken; + } + + /// The cached V1 compatibility projection for this expression. + BinaryExpressionV1Impl get binaryExpression => + _binaryExpression ??= BinaryExpressionV1Impl._ifNull(this); + + @generated + @override + Token get endToken { + return rightOperand.endToken; + } + + @generated + @override + ExpressionImpl get leftOperand => _leftOperand; + + @DoNotGenerate(reason: 'Keeps the cached V1 projection synchronized') + set leftOperand(ExpressionImpl leftOperand) { + _leftOperand = _becomeParentOf2(leftOperand); + _binaryExpression?._attachV1Children(); + } + + @override + Precedence get precedence => Precedence.ifNull; + + @generated + @override + ExpressionImpl get rightOperand => _rightOperand; + + @DoNotGenerate(reason: 'Keeps the cached V1 projection synchronized') + set rightOperand(ExpressionImpl rightOperand) { + _rightOperand = _becomeParentOf2(rightOperand); + _binaryExpression?._attachV1Children(); + } + + @generated + @override + AstNodeApi get _astNodeApi => AstNodeApi.v2; + + @generated + @override + ChildEntities get _childEntities { + throw StateError('IfNull is not in the V1 AST view.'); + } + + @generated + @override + ChildEntities get _childEntities2 => ChildEntities() + ..addNode('leftOperand', leftOperand) + ..addToken('operator', operator) + ..addNode('rightOperand', rightOperand); + + @generated + @ToBeDeprecated('Use accept2 instead.') + @override + E? accept<E>(AstVisitor<E> visitor) { + throw StateError('IfNull is not in the V1 AST view.'); + } + + @generated + @experimental + @override + E? accept2<E>(AstVisitor2<E> visitor) => visitor.visitIfNull(this); + + @generated + @override + bool isInValueExpressionSlot(AstNode child) { + assert(identical(child.parent2, this)); + return true; + } + + @generated + @override + void removeChild(AstNodeImpl oldNode) { + if (identical(leftOperand, oldNode)) { + throw UnsupportedError("Cannot remove required child 'leftOperand'."); + } + if (identical(rightOperand, oldNode)) { + throw UnsupportedError("Cannot remove required child 'rightOperand'."); + } + super.removeChild(oldNode); + } + + @generated + @override + void replaceChild(AstNodeImpl oldNode, AstNodeImpl newNode) { + if (identical(leftOperand, oldNode)) { + leftOperand = newNode as ExpressionImpl; + return; + } + if (identical(rightOperand, oldNode)) { + rightOperand = newNode as ExpressionImpl; + return; + } + super.replaceChild(oldNode, newNode); + } + + @DoNotGenerate(reason: 'Dispatches the canonical V2 node to the resolver') + @override + void resolveExpression(ResolverVisitor resolver, TypeImpl contextType) { + resolver.visitIfNull(this, contextType: contextType); + } + + @generated + @ToBeDeprecated('Use visitChildren2 instead.') + @override + void visitChildren(AstVisitor visitor) { + throw StateError('IfNull is not in the V1 AST view.'); + } + + @generated + @experimental + @override + void visitChildren2(AstVisitor2 visitor) { + leftOperand.accept2(visitor); + rightOperand.accept2(visitor); + } + + /// Visits the children of this node. + /// + /// If a specific hook is provided for a child, it is called instead of + /// dispatching the [visitor] to the child. It is the responsibility of the + /// hook to visit the child. + @generated + @experimental + void visitChildrenWithHooks( + AstVisitor2 visitor, { + void Function(ExpressionImpl)? visitLeftOperand, + void Function(ExpressionImpl)? visitRightOperand, + }) { + if (visitLeftOperand != null) { + visitLeftOperand(leftOperand); + } else { + leftOperand.accept2(visitor); + } + if (visitRightOperand != null) { + visitRightOperand(rightOperand); + } else { + rightOperand.accept2(visitor); + } + } + + @generated + @override + AstNodeImpl? _childContainingRange(int rangeOffset, int rangeEnd) { + throw StateError('IfNull is not in the V1 AST view.'); + } + + @generated + @override + AstNodeImpl? _childContainingRange2(int rangeOffset, int rangeEnd) { + if (leftOperand._containsOffset(rangeOffset, rangeEnd)) { + return leftOperand; + } + if (rightOperand._containsOffset(rangeOffset, rangeEnd)) { + return rightOperand; + } + return null; + } +} + /// An if statement. /// /// ifStatement ::= @@ -44844,6 +45063,9 @@ if (node is ConstructorTearOffImpl) { return node.constructorReference; } + if (node is IfNullImpl) { + return node.binaryExpression; + } if (node is LogicalNotImpl) { return node.prefixExpression; }
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.g.dart b/pkg/analyzer/lib/src/dart/ast/ast.g.dart index 8913b01..5b74900 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.g.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.g.dart
@@ -604,6 +604,9 @@ R? visitIfElement(IfElement node); + @experimental + R? visitIfNull(IfNull node); + R? visitIfStatement(IfStatement node); R? visitImplementsClause(ImplementsClause node);
diff --git a/pkg/analyzer/lib/src/dart/ast/constant_evaluator.dart b/pkg/analyzer/lib/src/dart/ast/constant_evaluator.dart index e7811e6..1235cd2 100644 --- a/pkg/analyzer/lib/src/dart/ast/constant_evaluator.dart +++ b/pkg/analyzer/lib/src/dart/ast/constant_evaluator.dart
@@ -151,6 +151,8 @@ if (leftOperand is bool && rightOperand is bool) { return leftOperand || rightOperand; } + } else if (node.operator.type == TokenType.QUESTION_QUESTION) { + return leftOperand ?? rightOperand; } else if (node.operator.type == TokenType.CARET) { // integer or {@code null} if (leftOperand is int && rightOperand is int) { @@ -560,6 +562,19 @@ Object? visitDoubleLiteral(DoubleLiteral node) => node.value; @override + Object? visitIfNull(IfNull node) { + var leftOperand = node.leftOperand.accept2(this); + if (identical(leftOperand, NOT_A_CONSTANT)) { + return leftOperand; + } + var rightOperand = node.rightOperand.accept2(this); + if (identical(rightOperand, NOT_A_CONSTANT)) { + return rightOperand; + } + return leftOperand ?? rightOperand; + } + + @override Object? visitIntegerLiteral(IntegerLiteral node) => node.value; @override
diff --git a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart index 5cf6d74..f40a9c4 100644 --- a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart +++ b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
@@ -772,6 +772,15 @@ } @override + void visitIfNull(IfNull node) { + _writeOperand(node, node.leftOperand); + sink.write(' '); + sink.write(node.operator.lexeme); + sink.write(' '); + _writeOperand(node, node.rightOperand); + } + + @override void visitIfStatement(IfStatement node) { sink.write('if ('); _visitNode(node.expression2);
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart index f87a49f..252d1e3 100644 --- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart +++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -693,20 +693,6 @@ return leftResult; } - if (operatorType == TokenType.QUESTION_QUESTION) { - if (!leftResult.isNull) { - var error = _reportNotPotentialConstants(node.rightOperand2); - if (error is InvalidConstant) { - return error; - } - } - return _dartObjectComputer.lazyQuestionQuestion( - node, - leftResult, - () => evaluateConstant(node.rightOperand2), - ); - } - // Evaluate eager operators. var rightResult = evaluateConstant(node.rightOperand2); if (rightResult is! DartObjectImpl) { @@ -1034,6 +1020,25 @@ } @override + Constant visitIfNull(IfNull node) { + var leftResult = evaluateConstant(node.leftOperand); + if (leftResult is! DartObjectImpl) { + return leftResult; + } + if (!leftResult.isNull) { + var error = _reportNotPotentialConstants(node.rightOperand); + if (error is InvalidConstant) { + return error; + } + } + return _dartObjectComputer.lazyQuestionQuestion( + node, + leftResult, + () => evaluateConstant(node.rightOperand), + ); + } + + @override Constant visitIntegerLiteral(IntegerLiteral node) { if (node.staticType == _typeProvider.doubleType) { return DartObjectImpl(
diff --git a/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart b/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart index 17da2f5..5af9428 100644 --- a/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart +++ b/pkg/analyzer/lib/src/dart/constant/potentially_constant.dart
@@ -134,6 +134,12 @@ return; } + if (node is IfNull) { + collect(node.leftOperand); + collect(node.rightOperand); + return; + } + if (node is LogicalAnd) { collect(node.leftOperand); collect(node.rightOperand);
diff --git a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart index 34c6659..fd99fd5 100644 --- a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
@@ -46,11 +46,6 @@ return; } - if (operator == TokenType.QUESTION_QUESTION) { - _resolveIfNull(node, contextType: contextType); - return; - } - if (operator.isUserDefinableOperator && operator.isBinaryOperator) { _resolveUserDefinable(node, contextType: contextType); return; @@ -68,6 +63,79 @@ _resolveUnsupportedOperator(node); } + void resolveIfNull(IfNullImpl node, {required TypeImpl contextType}) { + var left = node.leftOperand; + var right = node.rightOperand; + var flow = _resolver.flowAnalysis.flow; + + // An if-null expression `E` of the form `e1 ?? e2` with context type `K` is + // analyzed as follows: + // + // - Let `T1` be the type of `e1` inferred with context type `K?`. + _resolver.analyzeExpression( + left, + SharedTypeSchemaView(_typeSystem.makeNullable(contextType)), + ); + left = _resolver.popRewrite()!; + var t1 = left.typeOrThrow; + + // - Let `T2` be the type of `e2` inferred with context type `J`, where: + // - If `K` is `_`, `J = T1`. + TypeImpl j; + if (contextType is DynamicType || + contextType is InvalidType || + contextType is UnknownInferredType) { + j = t1; + } else + // - Otherwise, `J = K`. + { + j = contextType; + } + flow?.ifNullExpression_rightBegin( + _resolver.flowAnalysis.getExpressionInfo(left), + SharedTypeView(t1), + ); + _resolver.analyzeExpression(right, SharedTypeSchemaView(j)); + right = _resolver.popRewrite()!; + flow?.ifNullExpression_end(); + var t2 = right.typeOrThrow; + + // - Let `T` be `UP(NonNull(T1), T2)`. + var nonNullT1 = _typeSystem.promoteToNonNull(t1); + var t = _typeSystem.leastUpperBound(nonNullT1, t2); + + // - Let `S` be the greatest closure of `K`. + var s = _resolver.operations + .greatestClosureOfSchema(SharedTypeSchemaView(contextType)) + .unwrapTypeView<TypeImpl>(); + + DartType staticType; + // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. + if (!_resolver.definingLibrary.featureSet.isEnabled( + Feature.inference_update_3, + )) { + staticType = t; + } else + // - If `T <: S`, then the type of `E` is `T`. + if (_typeSystem.isSubtypeOf(t, s)) { + staticType = t; + } else + // - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of `E` is + // `S`. + if (_typeSystem.isSubtypeOf(nonNullT1, s) && + _typeSystem.isSubtypeOf(t2, s)) { + staticType = s; + } else + // - Otherwise, the type of `E` is `T`. + { + staticType = t; + } + + node.recordStaticType(staticType, resolver: _resolver); + + _resolver.checkForArgumentTypeNotAssignableForArgument(right); + } + void resolveLogicalAnd(LogicalAndImpl node) { _resolveLogicalExpression( node, @@ -183,82 +251,6 @@ } } - void _resolveIfNull( - BinaryExpressionImpl node, { - required TypeImpl contextType, - }) { - var left = node.leftOperand2; - var right = node.rightOperand2; - var flow = _resolver.flowAnalysis.flow; - - // An if-null expression `E` of the form `e1 ?? e2` with context type `K` is - // analyzed as follows: - // - // - Let `T1` be the type of `e1` inferred with context type `K?`. - _resolver.analyzeExpression( - left, - SharedTypeSchemaView(_typeSystem.makeNullable(contextType)), - ); - left = _resolver.popRewrite()!; - var t1 = left.typeOrThrow; - - // - Let `T2` be the type of `e2` inferred with context type `J`, where: - // - If `K` is `_`, `J = T1`. - TypeImpl j; - if (contextType is DynamicType || - contextType is InvalidType || - contextType is UnknownInferredType) { - j = t1; - } else - // - Otherwise, `J = K`. - { - j = contextType; - } - flow?.ifNullExpression_rightBegin( - _resolver.flowAnalysis.getExpressionInfo(left), - SharedTypeView(t1), - ); - _resolver.analyzeExpression(right, SharedTypeSchemaView(j)); - right = _resolver.popRewrite()!; - flow?.ifNullExpression_end(); - var t2 = right.typeOrThrow; - - // - Let `T` be `UP(NonNull(T1), T2)`. - var nonNullT1 = _typeSystem.promoteToNonNull(t1); - var t = _typeSystem.leastUpperBound(nonNullT1, t2); - - // - Let `S` be the greatest closure of `K`. - var s = _resolver.operations - .greatestClosureOfSchema(SharedTypeSchemaView(contextType)) - .unwrapTypeView<TypeImpl>(); - - DartType staticType; - // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. - if (!_resolver.definingLibrary.featureSet.isEnabled( - Feature.inference_update_3, - )) { - staticType = t; - } else - // - If `T <: S`, then the type of `E` is `T`. - if (_typeSystem.isSubtypeOf(t, s)) { - staticType = t; - } else - // - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of `E` is - // `S`. - if (_typeSystem.isSubtypeOf(nonNullT1, s) && - _typeSystem.isSubtypeOf(t2, s)) { - staticType = s; - } else - // - Otherwise, the type of `E` is `T`. - { - staticType = t; - } - - node.recordStaticType(staticType, resolver: _resolver); - - _resolver.checkForArgumentTypeNotAssignableForArgument(right); - } - void _resolveLogicalExpression( ExpressionImpl node, { required ExpressionImpl leftOperand,
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart index 65f74dd..e51fac9 100644 --- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart +++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -1156,6 +1156,14 @@ } @override + void visitIfNull(IfNull node) { + node.leftOperand.accept2(this); + assignedVariables.beginNode(); + node.rightOperand.accept2(this); + assignedVariables.endNode(node); + } + + @override void visitIfStatement(covariant IfStatementImpl node) { _visitIf(node); }
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart index 65c3860..10aba9c 100644 --- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart +++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -1537,6 +1537,15 @@ expression.rightOperand2, addTo: expressions, ); + } else if (expression is IfNull) { + _getSubExpressionsMarkedDoNotStore( + expression.leftOperand, + addTo: expressions, + ); + _getSubExpressionsMarkedDoNotStore( + expression.rightOperand, + addTo: expressions, + ); } else if (expression is FunctionExpression) { var body = expression.body; if (body is ExpressionFunctionBody) {
diff --git a/pkg/analyzer/lib/src/error/dead_code_verifier.dart b/pkg/analyzer/lib/src/error/dead_code_verifier.dart index 700f618..65de3af 100644 --- a/pkg/analyzer/lib/src/error/dead_code_verifier.dart +++ b/pkg/analyzer/lib/src/error/dead_code_verifier.dart
@@ -280,6 +280,8 @@ } } else if (parent is BinaryExpression) { offset = parent.operator.offset; + } else if (parent is IfNull) { + offset = parent.operator.offset; } if (parent is ConstructorInitializer) { _diagnosticReporter.report( @@ -311,6 +313,9 @@ } else if (parent is BinaryExpression) { offset = parent.operator.offset; node = parent.rightOperand2; + } else if (parent is IfNull) { + offset = parent.operator.offset; + node = parent.rightOperand; } else if (parent is LogicalAnd) { offset = parent.operator.offset; node = parent.rightOperand;
diff --git a/pkg/analyzer/lib/src/error/use_result_verifier.dart b/pkg/analyzer/lib/src/error/use_result_verifier.dart index a66b6db..99716bf 100644 --- a/pkg/analyzer/lib/src/error/use_result_verifier.dart +++ b/pkg/analyzer/lib/src/error/use_result_verifier.dart
@@ -203,6 +203,7 @@ // assignment. parent is AssignmentExpression || parent is BinaryExpression || + parent is IfNull || parent is ConstructorFieldInitializer || parent is DoStatement || parent is ExpressionFunctionBody ||
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart index 51685a7..977d84f 100644 --- a/pkg/analyzer/lib/src/fasta/ast_builder.dart +++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -1081,6 +1081,12 @@ var left = pop() as ExpressionImpl; reportErrorIfSuper(right); var expression = switch (operatorToken.type) { + TokenType.QUESTION_QUESTION => IfNullImpl( + leftOperand: left, + operator: operatorToken, + rightOperand: right, + ), + TokenType.AMPERSAND_AMPERSAND => LogicalAndImpl( leftOperand: left, operator: operatorToken,
diff --git a/pkg/analyzer/lib/src/fine/manifest_ast.dart b/pkg/analyzer/lib/src/fine/manifest_ast.dart index 5122099..cead9fb 100644 --- a/pkg/analyzer/lib/src/fine/manifest_ast.dart +++ b/pkg/analyzer/lib/src/fine/manifest_ast.dart
@@ -325,6 +325,11 @@ } @override + void visitIfNull(IfNull node) { + node.visitChildren2(this); + } + + @override void visitImportPrefixReference(ImportPrefixReference node) { _addElement(node.element); }
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index 3e3ac28..f1780ff 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -464,13 +464,6 @@ @override void visitBinaryExpression(covariant BinaryExpressionImpl node) { - TokenType type = node.operator.type; - if (type == TokenType.QUESTION_QUESTION) { - _checkForDeadNullCoalesce( - node.leftOperand2.staticType!, - node.rightOperand2, - ); - } checkForUseOfVoidResult(node.leftOperand2); _constArgumentsVerifier.visitBinaryExpression(node); @@ -1406,6 +1399,13 @@ } @override + void visitIfNull(covariant IfNullImpl node) { + _checkForDeadNullCoalesce(node.leftOperand.staticType!, node.rightOperand); + checkForUseOfVoidResult(node.leftOperand); + super.visitIfNull(node); + } + + @override void visitImportDirective(ImportDirective node) { var importElement = node.libraryImport; if (node.prefix != null) {
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index d6656f1..c981e5c 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -3257,6 +3257,21 @@ } @override + void visitIfNull( + covariant IfNullImpl node, { + TypeImpl contextType = UnknownInferredType.instance, + }) { + inferenceLogWriter?.enterExpression(node, contextType); + checkUnreachableNode(node); + _binaryExpressionResolver.resolveIfNull(node, contextType: contextType); + _insertImplicitCallReference( + insertGenericFunctionInstantiation(node, contextType: contextType), + contextType: contextType, + ); + inferenceLogWriter?.exitExpression(node); + } + + @override void visitIfStatement(covariant IfStatementImpl node) { inferenceLogWriter?.enterStatement(node); checkUnreachableNode(node); @@ -4831,8 +4846,7 @@ // conditional expression. return true; } - if (parent is BinaryExpression && - parent.operator.type == TokenType.QUESTION_QUESTION) { + if (parent is IfNull) { // Do not perform an "implicit tear-off conversion" on the branches of a // `??` operator. return true;
diff --git a/pkg/analyzer/lib/src/lint/linter_visitor.g.dart b/pkg/analyzer/lib/src/lint/linter_visitor.g.dart index 4e6424a..4866a39 100644 --- a/pkg/analyzer/lib/src/lint/linter_visitor.g.dart +++ b/pkg/analyzer/lib/src/lint/linter_visitor.g.dart
@@ -1750,6 +1750,13 @@ node.visitChildren2(this); } + @experimental + @override + void visitIfNull(IfNull node) { + _runSubscriptions(node, _registry._forIfNull); + node.visitChildren2(this); + } + @override void visitIfStatement(IfStatement node) { _runSubscriptions(node, _registry._forIfStatement); @@ -4383,6 +4390,8 @@ final List<_Subscription2<IfElement>> _forIfElement = []; + final List<_Subscription2<IfNull>> _forIfNull = []; + final List<_Subscription2<IfStatement>> _forIfStatement = []; final List<_Subscription2<ImplementsClause>> _forImplementsClause = []; @@ -5287,6 +5296,12 @@ } @override + void addIfNull(AbstractAnalysisRule rule, AstVisitor2 visitor) { + _hasNodeProcessors = true; + _forIfNull.add(_Subscription2(rule, visitor, _getTimer(rule))); + } + + @override void addIfStatement(AbstractAnalysisRule rule, AstVisitor2 visitor) { _hasNodeProcessors = true; _forIfStatement.add(_Subscription2(rule, visitor, _getTimer(rule)));
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart index 82ae038..2dccf95 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -647,6 +647,18 @@ ); } + IfNull _readIfNull() { + var leftOperand = _readNode() as ExpressionImpl; + var rightOperand = _readNode() as ExpressionImpl; + var node = IfNullImpl( + leftOperand: leftOperand, + operator: Tokens.questionQuestion(), + rightOperand: rightOperand, + ); + _readExpressionResolution(node); + return node; + } + ImplicitCallReference _readImplicitCallReference() { var expression = _readNode() as ExpressionImpl; var typeArguments = _readOptionalNode() as TypeArgumentListImpl?; @@ -984,6 +996,8 @@ return _readInterpolationString(); case Tag.IsExpression: return _readIsExpression(); + case Tag.IfNull: + return _readIfNull(); case Tag.ListLiteral: return _readListLiteral(); case Tag.LogicalAnd:
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart index cd9ea5e..6b93591 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
@@ -98,6 +98,8 @@ static const int LogicalNot = 124; static const int LogicalAnd = 125; static const int LogicalOr = 126; + static const int IfNull = 127; + static const int NullLiteral = 49; static const int ParenthesizedExpression = 53; static const int PostfixExpression = 94;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_tokens.dart b/pkg/analyzer/lib/src/summary2/ast_binary_tokens.dart index a5b134b..40a0b4b 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_tokens.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_tokens.dart
@@ -170,6 +170,9 @@ static Token questionPeriodPeriod() => TokenFactory.tokenFromType(TokenType.QUESTION_PERIOD_PERIOD); + static Token questionQuestion() => + TokenFactory.tokenFromType(TokenType.QUESTION_QUESTION); + static Token required_() => TokenFactory.tokenFromKeyword(Keyword.REQUIRED); static Token rethrow_() => TokenFactory.tokenFromKeyword(Keyword.RETHROW);
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart index 0ddb792..cd2c6f5 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -367,6 +367,14 @@ } @override + void visitIfNull(IfNull node) { + _writeByte(Tag.IfNull); + _writeNode(node.leftOperand); + _writeNode(node.rightOperand); + _storeExpression(node); + } + + @override void visitImplicitCallReference(ImplicitCallReference node) { _writeByte(Tag.ImplicitCallReference); _writeNode(node.expression2);
diff --git a/pkg/analyzer/lib/src/summary2/informative_data.dart b/pkg/analyzer/lib/src/summary2/informative_data.dart index 0ca7fb0..bb40735 100644 --- a/pkg/analyzer/lib/src/summary2/informative_data.dart +++ b/pkg/analyzer/lib/src/summary2/informative_data.dart
@@ -1990,6 +1990,12 @@ } @override + void visitIfNull(IfNull node) { + _tokenOrNull(node.operator); + super.visitIfNull(node); + } + + @override void visitImportPrefixReference(ImportPrefixReference node) { _tokenOrNull(node.name); _tokenOrNull(node.period);
diff --git a/pkg/analyzer/lib/src/test_utilities/find_node.dart b/pkg/analyzer/lib/src/test_utilities/find_node.dart index 1ff5a14..984f88b 100644 --- a/pkg/analyzer/lib/src/test_utilities/find_node.dart +++ b/pkg/analyzer/lib/src/test_utilities/find_node.dart
@@ -50,6 +50,8 @@ ConstructorTearOff get singleConstructorTearOff => _single(); + IfNull get singleIfNull => _single(); + LogicalAnd get singleLogicalAnd => _single(); LogicalNot get singleLogicalNot => _single(); @@ -66,6 +68,10 @@ return _node(search, (node) => node is ConstructorTearOff); } + IfNull ifNull(String search) { + return _node(search, (node) => node is IfNull); + } + LogicalAnd logicalAnd(String search) { return _node(search, (node) => node is LogicalAnd); }
diff --git a/pkg/analyzer/lib/src/util/ast_data_extractor.dart b/pkg/analyzer/lib/src/util/ast_data_extractor.dart index f97ed28..ec78fe2 100644 --- a/pkg/analyzer/lib/src/util/ast_data_extractor.dart +++ b/pkg/analyzer/lib/src/util/ast_data_extractor.dart
@@ -252,6 +252,8 @@ offset = node.question.offset; } else if (node is BinaryExpression) { offset = node.operator.offset; + } else if (node is IfNull) { + offset = node.operator.offset; } else if (node is LogicalAnd) { offset = node.operator.offset; } else if (node is LogicalOr) {
diff --git a/pkg/analyzer/lib/src/wolf/ir/ast_to_ir.dart b/pkg/analyzer/lib/src/wolf/ir/ast_to_ir.dart index 8444ab0..07609a3 100644 --- a/pkg/analyzer/lib/src/wolf/ir/ast_to_ir.dart +++ b/pkg/analyzer/lib/src/wolf/ir/ast_to_ir.dart
@@ -385,27 +385,7 @@ // Stack: (lhs == rhs) ir.not(); // Stack: (lhs != rhs) - case TokenType.QUESTION_QUESTION: - ir.block(0, 1); - // Stack: BLOCK(1) - dispatchNode(node.leftOperand2); - // Stack: BLOCK(1) lhs - ir.dup(); - // Stack: BLOCK(1) lhs lhs - ir.literal(null_); - // Stack: BLOCK(1) lhs lhs null - ir.eq(); - // Stack: BLOCK(1) lhs (lhs == null) - ir.not(); - // Stack: BLOCK(1) lhs (lhs != null) - ir.brIf(0); - // Stack: BLOCK(1) lhs - ir.drop(); - // Stack: BLOCK(1) - dispatchNode(node.rightOperand2); - // Stack: BLOCK(1) rhs - ir.end(); - // Stack: result + case TokenType.AMPERSAND: case TokenType.BAR: case TokenType.CARET: @@ -633,6 +613,30 @@ } @override + Null visitIfNull(IfNull node) { + ir.block(0, 1); + // Stack: BLOCK(1) + dispatchNode(node.leftOperand); + // Stack: BLOCK(1) lhs + ir.dup(); + // Stack: BLOCK(1) lhs lhs + ir.literal(null_); + // Stack: BLOCK(1) lhs lhs null + ir.eq(); + // Stack: BLOCK(1) lhs (lhs == null) + ir.not(); + // Stack: BLOCK(1) lhs (lhs != null) + ir.brIf(0); + // Stack: BLOCK(1) lhs + ir.drop(); + // Stack: BLOCK(1) + dispatchNode(node.rightOperand); + // Stack: BLOCK(1) rhs + ir.end(); + // Stack: result + } + + @override Null visitIfStatement(IfStatement node) { if (node.caseClause != null) throw UnimplementedError('TODO(paulberry)'); var elseStatement = node.elseStatement;
diff --git a/pkg/analyzer/test/generated/complex_parser_test.dart b/pkg/analyzer/test/generated/complex_parser_test.dart index 4541f78..a521f26 100644 --- a/pkg/analyzer/test/generated/complex_parser_test.dart +++ b/pkg/analyzer/test/generated/complex_parser_test.dart
@@ -759,11 +759,17 @@ var node = parseResult.findNode.singleExpressionStatement.expression2; assertParsedNodeText(node, r''' ConditionalExpression - condition2: BinaryExpression - leftOperand2: SimpleIdentifier + condition2: IfNull + leftOperand: SimpleIdentifier token: a operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: b + condition(v1): BinaryExpression + leftOperand: SimpleIdentifier + token: a + operator: ?? + rightOperand: SimpleIdentifier token: b question: ? thenExpression2: SimpleIdentifier @@ -1422,15 +1428,15 @@ var node = parseResult.findNode.singleExpressionStatement.expression2; assertParsedNodeText(node, r''' -BinaryExpression - leftOperand2: BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: IfNull + leftOperand: SimpleIdentifier token: x operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier token: y operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier token: z '''); } @@ -1444,21 +1450,15 @@ var node = parseResult.findNode.singleExpressionStatement.expression2; assertParsedNodeText(node, r''' -BinaryExpression - leftOperand2: LogicalOr - leftOperand: SimpleIdentifier - token: x - operator: || - rightOperand: SimpleIdentifier - token: y - leftOperand(v1): BinaryExpression +IfNull + leftOperand: LogicalOr leftOperand: SimpleIdentifier token: x operator: || rightOperand: SimpleIdentifier token: y operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier token: z '''); } @@ -1472,17 +1472,11 @@ var node = parseResult.findNode.singleExpressionStatement.expression2; assertParsedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: x operator: ?? - rightOperand2: LogicalOr - leftOperand: SimpleIdentifier - token: y - operator: || - rightOperand: SimpleIdentifier - token: z - rightOperand(v1): BinaryExpression + rightOperand: LogicalOr leftOperand: SimpleIdentifier token: y operator: ||
diff --git a/pkg/analyzer/test/generated/function_reference_parser_test.dart b/pkg/analyzer/test/generated/function_reference_parser_test.dart index c4b3401..08117a4 100644 --- a/pkg/analyzer/test/generated/function_reference_parser_test.dart +++ b/pkg/analyzer/test/generated/function_reference_parser_test.dart
@@ -1059,15 +1059,32 @@ operator: < rightOperand2: SimpleIdentifier token: b - BinaryExpression - leftOperand2: BinaryExpression + IfNull + leftOperand: BinaryExpression leftOperand2: SimpleIdentifier token: c operator: > rightOperand2: SimpleIdentifier token: <empty> <synthetic> operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: d + arguments(v1) + BinaryExpression + leftOperand: SimpleIdentifier + token: a + operator: < + rightOperand: SimpleIdentifier + token: b + BinaryExpression + leftOperand: BinaryExpression + leftOperand: SimpleIdentifier + token: c + operator: > + rightOperand: SimpleIdentifier + token: <empty> <synthetic> + operator: ?? + rightOperand: SimpleIdentifier token: d rightParenthesis: ) ''');
diff --git a/pkg/analyzer/test/generated/nnbd_parser_test.dart b/pkg/analyzer/test/generated/nnbd_parser_test.dart index 3340450..855bf72 100644 --- a/pkg/analyzer/test/generated/nnbd_parser_test.dart +++ b/pkg/analyzer/test/generated/nnbd_parser_test.dart
@@ -316,11 +316,17 @@ leftBracket: { statements ExpressionStatement - expression2: BinaryExpression - leftOperand2: SimpleIdentifier + expression2: IfNull + leftOperand: SimpleIdentifier token: X operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: x2 + expression(v1): BinaryExpression + leftOperand: SimpleIdentifier + token: X + operator: ?? + rightOperand: SimpleIdentifier token: x2 semicolon: ; rightBracket: }
diff --git a/pkg/analyzer/test/src/dart/ast/constant_evaluator_test.dart b/pkg/analyzer/test/src/dart/ast/constant_evaluator_test.dart index 03e2a30..1505133 100644 --- a/pkg/analyzer/test/src/dart/ast/constant_evaluator_test.dart +++ b/pkg/analyzer/test/src/dart/ast/constant_evaluator_test.dart
@@ -279,6 +279,26 @@ expect(value, null); } + void test_ifNull_leftNotConstant() { + var value = _getConstantValue("a ?? 1"); + expect(value, notAConstant); + } + + void test_ifNull_leftNotNull() { + var value = _getConstantValue("0 ?? 1"); + expect(value, 0); + } + + void test_ifNull_leftNull() { + var value = _getConstantValue("null ?? 1"); + expect(value, 1); + } + + void test_ifNull_rightNotConstant() { + var value = _getConstantValue("0 ?? a"); + expect(value, notAConstant); + } + void test_literal_boolean_false() { var value = _getConstantValue("false"); expect(value, false);
diff --git a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart index 8106151..708698f 100644 --- a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart +++ b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
@@ -2435,6 +2435,14 @@ _assertSource(code, node); } + void test_visitIfNull() { + var parseResult = parseTestCodeWithDiagnostics(''' +final x = a ?? (b ?? c); +'''); + var node = parseResult.findNode.ifNull('a ??'); + _assertSource('a ?? (b ?? c)', node); + } + void test_visitIfStatement_withElse() { var code = 'if (c) {} else {}'; var parseResult = parseTestCodeWithDiagnostics('''
diff --git a/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart b/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart index c78c9f8..54be30c 100644 --- a/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart
@@ -192,15 +192,26 @@ // `c` is on the LHS of an if-null expression, so implicit call tearoff // logic should not apply to it. // Therefore the type of `c ?? a` should be `A`. - var node = result.findNode.binary('c ?? a'); + var node = result.findNode.ifNull('c ?? a'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: c element: <testLibrary>::@function::f::@formalParameter::c staticType: C operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: a + element: <testLibrary>::@function::f::@formalParameter::a + staticType: A + staticType: A +BinaryExpression + leftOperand: SimpleIdentifier + token: c + element: <testLibrary>::@function::f::@formalParameter::c + staticType: C + operator: ?? + rightOperand: SimpleIdentifier token: a correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::a @@ -225,13 +236,24 @@ var node = result.findNode.implicitCallReference('c1 ?? c2'); assertResolvedNodeText(node, r''' ImplicitCallReference - expression2: BinaryExpression - leftOperand2: SimpleIdentifier + expression2: IfNull + leftOperand: SimpleIdentifier token: c1 element: <testLibrary>::@function::foo::@formalParameter::c1 staticType: C? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: c2 + element: <testLibrary>::@function::foo::@formalParameter::c2 + staticType: C + staticType: C + expression(v1): BinaryExpression + leftOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::foo::@formalParameter::c1 + staticType: C? + operator: ?? + rightOperand: SimpleIdentifier token: c2 correspondingParameter: <null> element: <testLibrary>::@function::foo::@formalParameter::c2
diff --git a/pkg/analyzer/test/src/dart/resolution/binary_expression_test.dart b/pkg/analyzer/test/src/dart/resolution/binary_expression_test.dart index 6f37761..f1e2e15 100644 --- a/pkg/analyzer/test/src/dart/resolution/binary_expression_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/binary_expression_test.dart
@@ -223,10 +223,10 @@ int g() => f(null) ?? 0; '''); - var node = result.findNode.binary('?? 0'); + var node = result.findNode.ifNull('?? 0'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: MethodInvocation +IfNull + leftOperand: MethodInvocation methodName: SimpleIdentifier token: f element: <testLibrary>::@function::f @@ -246,7 +246,32 @@ typeArgumentTypes int? operator: ?? - rightOperand2: IntegerLiteral + rightOperand: IntegerLiteral + literal: 0 + staticType: int + staticType: int +BinaryExpression + leftOperand: MethodInvocation + methodName: SimpleIdentifier + token: f + element: <testLibrary>::@function::f + staticType: T Function<T>(T) + argumentList: ArgumentList + leftParenthesis: ( + arguments + NullLiteral + literal: null + correspondingParameter: SubstitutedFormalParameterElementImpl + baseElement: <testLibrary>::@function::f::@formalParameter::t + substitution: {T: int?} + staticType: Null + rightParenthesis: ) + staticInvokeType: int? Function(int?) + staticType: int? + typeArgumentTypes + int? + operator: ?? + rightOperand: IntegerLiteral literal: 0 correspondingParameter: <null> staticType: int @@ -271,20 +296,31 @@ } '''); - var node = result.findNode.binary('c1 ?? c2'); + var node = result.findNode.ifNull('c1 ?? c2'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: c1 element: <testLibrary>::@function::f::@formalParameter::c1 staticType: C1? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: c2 + element: <testLibrary>::@function::f::@formalParameter::c2 + staticType: C2 + correspondingParameter: <null> + staticType: A +BinaryExpression + leftOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::f::@formalParameter::c1 + staticType: C1? + operator: ?? + rightOperand: SimpleIdentifier token: c2 correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::c2 staticType: C2 - correspondingParameter: <null> element: <null> staticInvokeType: null staticType: A @@ -298,15 +334,26 @@ } '''); - var node = result.findNode.binary('x ?? y'); + var node = result.findNode.ifNull('x ?? y'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: x element: <testLibrary>::@function::f::@formalParameter::x staticType: int? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: y + element: <testLibrary>::@function::f::@formalParameter::y + staticType: int + staticType: int +BinaryExpression + leftOperand: SimpleIdentifier + token: x + element: <testLibrary>::@function::f::@formalParameter::x + staticType: int? + operator: ?? + rightOperand: SimpleIdentifier token: y correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::y @@ -324,15 +371,26 @@ } '''); - var node = result.findNode.binary('x ?? y'); + var node = result.findNode.ifNull('x ?? y'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: x element: <testLibrary>::@function::f::@formalParameter::x staticType: int? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: y + element: <testLibrary>::@function::f::@formalParameter::y + staticType: double? + staticType: num? +BinaryExpression + leftOperand: SimpleIdentifier + token: x + element: <testLibrary>::@function::f::@formalParameter::x + staticType: int? + operator: ?? + rightOperand: SimpleIdentifier token: y correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::y @@ -350,15 +408,26 @@ } '''); - var node = result.findNode.binary('x ?? x'); + var node = result.findNode.ifNull('x ?? x'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: x element: <testLibrary>::@function::f::@formalParameter::x staticType: int? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: x + element: <testLibrary>::@function::f::@formalParameter::x + staticType: int? + staticType: int? +BinaryExpression + leftOperand: SimpleIdentifier + token: x + element: <testLibrary>::@function::f::@formalParameter::x + staticType: int? + operator: ?? + rightOperand: SimpleIdentifier token: x correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::x @@ -942,15 +1011,26 @@ } '''); - var node = result.findNode.binary('a ?? b'); + var node = result.findNode.ifNull('a ?? b'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: a element: <testLibrary>::@function::f::@formalParameter::a staticType: int? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: b + element: <testLibrary>::@function::f::@formalParameter::b + staticType: double + staticType: num +BinaryExpression + leftOperand: SimpleIdentifier + token: a + element: <testLibrary>::@function::f::@formalParameter::a + staticType: int? + operator: ?? + rightOperand: SimpleIdentifier token: b correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::b @@ -2054,14 +2134,28 @@ } '''); - var node = result.findNode.binary('c1 ?? c2'); - assertResolvedNodeText(node, r'''BinaryExpression - leftOperand2: SimpleIdentifier + var node = result.findNode.ifNull('c1 ?? c2'); + assertResolvedNodeText(node, r'''IfNull + leftOperand: SimpleIdentifier token: c1 element: <testLibrary>::@function::f::@formalParameter::c1 staticType: C1<int>? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: c2 + element: <testLibrary>::@function::f::@formalParameter::c2 + staticType: C2<double> + correspondingParameter: SubstitutedFormalParameterElementImpl + baseElement: <testLibrary>::@function::contextB1::@formalParameter::b1 + substitution: {T: Object?} + staticType: B1<Object?> +BinaryExpression + leftOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::f::@formalParameter::c1 + staticType: C1<int>? + operator: ?? + rightOperand: SimpleIdentifier token: c2 correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::c2 @@ -2089,19 +2183,30 @@ } '''); - var node = result.findNode.binary('b2 ?? c1'); - assertResolvedNodeText(node, r'''BinaryExpression - leftOperand2: SimpleIdentifier + var node = result.findNode.ifNull('b2 ?? c1'); + assertResolvedNodeText(node, r'''IfNull + leftOperand: SimpleIdentifier token: b2 element: <testLibrary>::@function::f::@formalParameter::b2 staticType: B2? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::f::@formalParameter::c1 + staticType: C1 + correspondingParameter: <null> + staticType: B2 +BinaryExpression + leftOperand: SimpleIdentifier + token: b2 + element: <testLibrary>::@function::f::@formalParameter::b2 + staticType: B2? + operator: ?? + rightOperand: SimpleIdentifier token: c1 correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::c1 staticType: C1 - correspondingParameter: <null> element: <null> staticInvokeType: null staticType: B2 @@ -2122,19 +2227,30 @@ } '''); - var node = result.findNode.binary('c1 ?? b2'); - assertResolvedNodeText(node, r'''BinaryExpression - leftOperand2: SimpleIdentifier + var node = result.findNode.ifNull('c1 ?? b2'); + assertResolvedNodeText(node, r'''IfNull + leftOperand: SimpleIdentifier token: c1 element: <testLibrary>::@function::f::@formalParameter::c1 staticType: C1? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: b2 + element: <testLibrary>::@function::f::@formalParameter::b2 + staticType: B2 + correspondingParameter: <null> + staticType: B2 +BinaryExpression + leftOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::f::@formalParameter::c1 + staticType: C1? + operator: ?? + rightOperand: SimpleIdentifier token: b2 correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::b2 staticType: B2 - correspondingParameter: <null> element: <null> staticInvokeType: null staticType: B2 @@ -2151,15 +2267,26 @@ B1 f(C1? c1, C2 c2) => c1 ?? c2; '''); - var node = result.findNode.binary('c1 ?? c2'); + var node = result.findNode.ifNull('c1 ?? c2'); assertResolvedNodeText(node, r''' -BinaryExpression - leftOperand2: SimpleIdentifier +IfNull + leftOperand: SimpleIdentifier token: c1 element: <testLibrary>::@function::f::@formalParameter::c1 staticType: C1? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: c2 + element: <testLibrary>::@function::f::@formalParameter::c2 + staticType: C2 + staticType: B1 +BinaryExpression + leftOperand: SimpleIdentifier + token: c1 + element: <testLibrary>::@function::f::@formalParameter::c1 + staticType: C1? + operator: ?? + rightOperand: SimpleIdentifier token: c2 correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::c2
diff --git a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart index 7ff48b5..d2a9492 100644 --- a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart
@@ -1956,13 +1956,24 @@ function2: PropertyAccess target2: ParenthesizedExpression leftParenthesis: ( - expression2: BinaryExpression - leftOperand2: SimpleIdentifier + expression2: IfNull + leftOperand: SimpleIdentifier token: a element: <testLibrary>::@function::f::@formalParameter::a staticType: A? operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: b + element: <testLibrary>::@function::f::@formalParameter::b + staticType: A + staticType: A + expression(v1): BinaryExpression + leftOperand: SimpleIdentifier + token: a + element: <testLibrary>::@function::f::@formalParameter::a + staticType: A? + operator: ?? + rightOperand: SimpleIdentifier token: b correspondingParameter: <null> element: <testLibrary>::@function::f::@formalParameter::b
diff --git a/pkg/analyzer/test/src/dart/resolution/type_literal_test.dart b/pkg/analyzer/test/src/dart/resolution/type_literal_test.dart index eb191d6..4afc183 100644 --- a/pkg/analyzer/test/src/dart/resolution/type_literal_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/type_literal_test.dart
@@ -708,7 +708,6 @@ name: C element: <testLibrary>::@class::C type: C - correspondingParameter: <null> staticType: Type '''); } @@ -736,7 +735,6 @@ name: C element: package:test/a.dart::@class::C type: C - correspondingParameter: <null> staticType: Type '''); } @@ -5906,7 +5904,6 @@ name: dynamic element: dynamic type: dynamic - correspondingParameter: <null> staticType: Type '''); } @@ -6642,7 +6639,6 @@ name: Never element: Never type: Never - correspondingParameter: <null> staticType: Type '''); } @@ -7671,7 +7667,6 @@ name: T element: #E0 T type: T - correspondingParameter: <null> staticType: Type '''); }
diff --git a/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart b/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart index 78cb645..3679075 100644 --- a/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart +++ b/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart
@@ -265,7 +265,7 @@ '''); } - test_topLevelGetter_binaryExpression() async { + test_topLevelGetter_ifNull() async { await resolveTestCodeWithDiagnostics(r''' import 'package:meta/meta.dart';
diff --git a/pkg/analyzer/test/src/diagnostics/return_of_do_not_store_test.dart b/pkg/analyzer/test/src/diagnostics/return_of_do_not_store_test.dart index 0849a1c..6a8ccf0 100644 --- a/pkg/analyzer/test/src/diagnostics/return_of_do_not_store_test.dart +++ b/pkg/analyzer/test/src/diagnostics/return_of_do_not_store_test.dart
@@ -122,7 +122,7 @@ '''); } - test_returnFromGetter_binaryExpression() async { + test_returnFromGetter_ifNull() async { await resolveTestCodeWithDiagnostics(r''' import 'package:meta/meta.dart';
diff --git a/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart b/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart index 6339b63..abf389b 100644 --- a/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart +++ b/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart
@@ -2018,11 +2018,17 @@ rightParenthesis: ) body: ExpressionFunctionBody functionDefinition: => - expression2: BinaryExpression - leftOperand2: SimpleIdentifier + expression2: IfNull + leftOperand: SimpleIdentifier token: x operator: ?? - rightOperand2: SimpleIdentifier + rightOperand: SimpleIdentifier + token: throw + expression(v1): BinaryExpression + leftOperand: SimpleIdentifier + token: x + operator: ?? + rightOperand: SimpleIdentifier token: throw semicolon: ; <synthetic> ''');
diff --git a/pkg/analyzer/test/src/summary/elements/const_test.dart b/pkg/analyzer/test/src/summary/elements/const_test.dart index 5fa4135..38c9a54 100644 --- a/pkg/analyzer/test/src/summary/elements/const_test.dart +++ b/pkg/analyzer/test/src/summary/elements/const_test.dart
@@ -9144,16 +9144,14 @@ #F1 hasImplicitType hasInitializer isConst isOriginDeclaration isStatic vIfNull (nameOffset:6) (firstTokenOffset:6) (offset:6) element: <testLibrary>::@topLevelVariable::vIfNull initializer: expression_0 - BinaryExpression - leftOperand2: IntegerLiteral + IfNull + leftOperand: IntegerLiteral literal: 1 @16 staticType: int operator: ?? @18 - rightOperand2: DoubleLiteral + rightOperand: DoubleLiteral literal: 2.0 @21 staticType: double - element: <null> - staticInvokeType: null staticType: num inducedGetter: #F2 getters
diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart index 6bfb274..600d6ca 100644 --- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart +++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
@@ -906,6 +906,16 @@ } @override + void visitIfNull(IfNull node) { + _sink.writeln('IfNull'); + _sink.withIndent(() { + _writeNamedChildEntities(node); + _writeParameterElement(node); + _writeType('staticType', node.staticType); + }); + } + + @override void visitIfStatement(IfStatement node) { _sink.writeln('IfStatement'); _sink.withIndent(() {
diff --git a/pkg/analyzer/test/src/wolf/ir/ast_to_ir_test.dart b/pkg/analyzer/test/src/wolf/ir/ast_to_ir_test.dart index 5ed37bb..ed59864 100644 --- a/pkg/analyzer/test/src/wolf/ir/ast_to_ir_test.dart +++ b/pkg/analyzer/test/src/wolf/ir/ast_to_ir_test.dart
@@ -700,7 +700,7 @@ test(Object? x, Object? y) => hook(x, 'x') ?? hook(y, 'y'); '''); analyze(result, result.findNode.functionDeclaration('test')); - check(astNodes)[result.findNode.binary("hook(x, 'x') ?? hook(y, 'y')")] + check(astNodes)[result.findNode.ifNull("hook(x, 'x') ?? hook(y, 'y')")] ..containsSubrange( astNodes[result.findNode.methodInvocation("hook(x, 'x')")]!, )