[analyzer] Dot shorthands: Update and add tests for const dot shorthands. Updated the AST binary reader/writer to handle dot shorthand invocations otherwise we crash when we try to evaluate a method invocation. Added a test for that in `const_test.dart`. Added a plethora of tests in `evaluation_test.dart` which led me to find a bug with not carrying over the `isDotShorthand` flag when we convert from a method invocation to a constructor invocation. Unit tests passing. Bug: https://github.com/dart-lang/sdk/issues/59835 Change-Id: Ide1cb7cffc0285ed7d54e9f7bc641d0d28c1d2e2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437881 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index de1a413..0051cc0 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -110,7 +110,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 = 487; + static const int DATA_VERSION = 488; /// 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 2fbde83..9785b48 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -7197,8 +7197,17 @@ base mixin DotShorthandMixin on ExpressionImpl { /// Whether the AST node is a dot shorthand and has a dot shorthand head - /// ([DotShorthandInvocation] or [DotShorthandPropertyAccess]) as its + /// ([DotShorthandInvocation], [DotShorthandConstructorInvocation] or + /// [DotShorthandPropertyAccess]) as its /// inner-most target. + /// + /// This is `false` and remains `false` when there is no dot shorthand head as + /// its inner-most target. When we are parsing and notice that we have a dot + /// shorthand head, we flip this flag to `true` and it remains `true` for that + /// expression. + /// + /// We use this flag to determine the correct context type to cache. This + /// cached context type is then used to resolve the dot shorthand head. bool isDotShorthand = false; }
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart index 9aa2225..576c8d2 100644 --- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart +++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -921,6 +921,11 @@ } @override + Constant visitDotShorthandInvocation(DotShorthandInvocation node) { + return _invalidConstantForMethodInvocation(node); + } + + @override Constant visitDotShorthandPropertyAccess( covariant DotShorthandPropertyAccessImpl node, ) { @@ -1150,20 +1155,7 @@ } } - // Some methods aren't resolved by the time we are evaluating it. We'll mark - // it and return immediately. - if (node.staticType is InvalidType) { - return InvalidConstant.forEntity( - entity: node, - diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT, - isUnresolved: true, - ); - } - - return InvalidConstant.forEntity( - entity: node, - diagnosticCode: CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION, - ); + return _invalidConstantForMethodInvocation(node); } @override @@ -2221,6 +2213,25 @@ return value; } + // Common invalid constants for method invocations and dot shorthand + // invocations. + Constant _invalidConstantForMethodInvocation(Expression node) { + // Some methods aren't resolved by the time we are evaluating it. We'll mark + // it and return immediately. + if (node.staticType is InvalidType) { + return InvalidConstant.forEntity( + entity: node, + diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT, + isUnresolved: true, + ); + } + + return InvalidConstant.forEntity( + entity: node, + diagnosticCode: CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION, + ); + } + /// Returns the first not-potentially constant error found with [node] or /// `null` if there are none. InvalidConstant? _reportNotPotentialConstants(AstNode node) {
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart index 644ae4d..27e93d0 100644 --- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -1288,13 +1288,16 @@ // [DotShorthandConstructorInvocation]. if (receiver.getNamedConstructor2(name) case ConstructorElementImpl element? when element.isAccessibleIn2(_resolver.definingLibrary)) { - var replacement = DotShorthandConstructorInvocationImpl( - constKeyword: null, - period: node.period, - constructorName: nameNode, - typeArguments: node.typeArguments, - argumentList: node.argumentList, - )..element = element; + var replacement = + DotShorthandConstructorInvocationImpl( + constKeyword: null, + period: node.period, + constructorName: nameNode, + typeArguments: node.typeArguments, + argumentList: node.argumentList, + ) + ..element = element + ..isDotShorthand = node.isDotShorthand; _resolver.replaceExpression(node, replacement); _resolver.flowAnalysis.transferTestData(node, replacement); return replacement;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart index 007151f..66576ff 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -65,6 +65,8 @@ return _readDefaultFormalParameter(); case Tag.DotShorthandConstructorInvocation: return _readDotShorthandConstructorInvocation(); + case Tag.DotShorthandInvocation: + return _readDotShorthandInvocation(); case Tag.DotShorthandPropertyAccess: return _readDotShorthandPropertyAccess(); case Tag.DottedName: @@ -482,6 +484,20 @@ return node; } + DotShorthandInvocation _readDotShorthandInvocation() { + var memberName = readNode() as SimpleIdentifierImpl; + var typeArguments = _readOptionalNode() as TypeArgumentListImpl?; + var arguments = readNode() as ArgumentListImpl; + var node = DotShorthandInvocationImpl( + period: Tokens.period(), + memberName: memberName, + typeArguments: typeArguments, + argumentList: arguments, + ); + _readInvocationExpression(node); + return node; + } + DotShorthandPropertyAccess _readDotShorthandPropertyAccess() { var propertyName = readNode() as SimpleIdentifierImpl; var node = DotShorthandPropertyAccessImpl(
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart index 0d90ee3..be52c2a 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
@@ -52,6 +52,7 @@ static const int DeclaredIdentifier = 90; static const int DefaultFormalParameter = 8; static const int DotShorthandConstructorInvocation = 114; + static const int DotShorthandInvocation = 115; static const int DotShorthandPropertyAccess = 113; static const int DottedName = 47; static const int DoubleLiteral = 9;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart index c093a9a..d6418e3 100644 --- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart +++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -240,6 +240,13 @@ } @override + void visitDotShorthandInvocation(DotShorthandInvocation node) { + _writeByte(Tag.DotShorthandInvocation); + _writeNode(node.memberName); + _storeInvocationExpression(node); + } + + @override void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) { _writeByte(Tag.DotShorthandPropertyAccess); _writeNode(node.propertyName);
diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart index f996ad7..1c7747c 100644 --- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart +++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
@@ -94,6 +94,171 @@ ); } + test_dotShorthand_enum_simple() async { + await resolveTestCode(''' +enum E { v1, v2 } +const E x1 = .v1; +const E x2 = .v2; +'''); + assertDartObjectText(_topLevelVar('x1'), r''' +E + _name: String v1 + index: int 0 + variable: <testLibrary>::@topLevelVariable::x1 +'''); + assertDartObjectText(_topLevelVar('x2'), r''' +E + _name: String v2 + index: int 1 + variable: <testLibrary>::@topLevelVariable::x2 +'''); + } + + test_dotShorthand_equalEqual_constructor() async { + await assertNoErrorsInCode(''' +class A { + const A(); +} + +const v = A() == .new(); +'''); + var result = _topLevelVar('v'); + assertDartObjectText(result, ''' +bool true + variable: <testLibrary>::@topLevelVariable::v +'''); + } + + test_dotShorthand_equalEqual_constructor_lhsShorthand() async { + await assertErrorsInCode( + ''' +class A { + const A(); +} + +const v = .new() == A(); +''', + [ + error( + CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, + 36, + 6, + ), + error(CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION, 37, 3), + ], + ); + } + + test_dotShorthand_equalEqual_field() async { + await assertNoErrorsInCode(''' +class A { + const A(); + static const A field = A(); +} + +const v = A() == .field; +'''); + var result = _topLevelVar('v'); + assertDartObjectText(result, ''' +bool true + variable: <testLibrary>::@topLevelVariable::v +'''); + } + + test_dotShorthand_equalEqual_method_error() async { + await assertErrorsInCode( + ''' +class A { + static A method() => A(); +} + +const v = A() == .method(); +''', + [ + error(CompileTimeErrorCode.CONST_WITH_NON_CONST, 51, 3), + error( + CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, + 51, + 3, + ), + ], + ); + } + + test_dotShorthand_method_invalid() async { + await assertErrorsInCode( + ''' +class A { + static A method() => A(); +} +const A a = .method(); +''', + [error(CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION, 52, 9)], + ); + } + + test_dotShorthand_missingContext_invocation() async { + await assertErrorsInCode( + ''' +const a = .new(); +''', + [ + error( + CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, + 10, + 6, + ), + error(CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION, 11, 3), + ], + ); + } + + test_dotShorthand_missingContext_propertyAccess() async { + await assertErrorsInCode( + ''' +const a = .id; +''', + [ + error(CompileTimeErrorCode.DOT_SHORTHAND_MISSING_CONTEXT, 10, 3), + error( + CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, + 10, + 3, + ), + ], + ); + } + + test_dotShorthand_propertyAccess() async { + await assertNoErrorsInCode(''' +class A { + const A(); + static const A field = A(); +} + +const A a = .field; +'''); + var result = _topLevelVar('a'); + assertDartObjectText(result, ''' +A + variable: <testLibrary>::@topLevelVariable::a +'''); + } + + test_dotShorthand_propertyAccess_enum() async { + await assertNoErrorsInCode(''' +enum E { a } +const E e = .a; +'''); + var result = _topLevelVar('e'); + assertDartObjectText(result, r''' +E + _name: String a + index: int 0 + variable: <testLibrary>::@topLevelVariable::e +'''); + } + test_enum_argument_methodInvocation() async { await assertErrorsInCode( ''' @@ -6108,6 +6273,162 @@ ); } + test_dotShorthand_assertInitializer_assertIsNot_false() async { + await assertErrorsInCode( + ''' +class A { + const A() : assert(0 is! int); +} + +const A a = .new(); +''', + [ + error(WarningCode.UNNECESSARY_TYPE_CHECK_FALSE, 31, 9), + error( + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, + 58, + 6, + contextMessages: [ + ExpectedContextMessage( + testFile, + 24, + 17, + text: + "The exception is 'The assertion in this constant expression failed.' and occurs here.", + ), + ], + ), + ], + ); + } + + test_dotShorthand_assertInitializer_assertIsNot_true() async { + await assertNoErrorsInCode(''' +class A { + const A() : assert(0 is! String); +} + +const A a = .new(); +'''); + var result = _topLevelVar('a'); + assertDartObjectText(result, ''' +A + variable: <testLibrary>::@topLevelVariable::a +'''); + } + + test_dotShorthand_assertInitializer_enum_false() async { + await assertErrorsInCode( + ''' +enum E { a, b } +class A { + const A(E e) : assert(e != .a); +} +const A a = .new(.a); +''', + [ + error( + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, + 74, + 8, + contextMessages: [ + ExpectedContextMessage( + testFile, + 43, + 15, + text: + "The exception is 'The assertion in this constant expression failed.' and occurs here.", + ), + ], + ), + ], + ); + } + + test_dotShorthand_assertInitializer_enum_true() async { + await assertNoErrorsInCode(''' +enum E { a, b } +class A { + const A(E e) : assert(e != .a); +} +const A a = .new(.b); +'''); + var result = _topLevelVar('a'); + assertDartObjectText(result, ''' +A + variable: <testLibrary>::@topLevelVariable::a +'''); + } + + test_dotShorthand_assertInitializer_simpleInSuperInitializer_true() async { + await assertErrorsInCode( + ''' +class A { + const A(): assert(1 is int); +} +class B extends A { + const B() : super(); +} +const B b = .new(); +''', + [error(WarningCode.UNNECESSARY_TYPE_CHECK_TRUE, 30, 8)], + ); + var result = _topLevelVar('b'); + assertDartObjectText(result, ''' +B + (super): A + variable: <testLibrary>::@topLevelVariable::b +'''); + } + + test_dotShorthand_bool_fromEnvironment() async { + await assertNoErrorsInCode(''' +const bool a = .fromEnvironment('a'); +const bool b = .fromEnvironment('b', defaultValue: true); +'''); + assertDartObjectText(_topLevelVar('a'), ''' +bool false + variable: <testLibrary>::@topLevelVariable::a +'''); + assertDartObjectText( + _evaluateConstant('a', declaredVariables: {'a': 'true'}), + ''' +bool true +''', + ); + + var bResult = _evaluateConstant( + 'b', + declaredVariables: {'b': 'bbb'}, + lexicalEnvironment: { + 'defaultValue': DartObjectImpl( + typeSystem, + typeProvider.boolType, + BoolState(true), + ), + }, + ); + assertDartObjectText(bResult, ''' +bool true +'''); + } + + test_dotShorthand_bool_hasEnvironment() async { + await assertNoErrorsInCode(''' +const bool a = .hasEnvironment('a'); +'''); + assertDartObjectText(_topLevelVar('a'), ''' +bool false + variable: <testLibrary>::@topLevelVariable::a +'''); + assertDartObjectText( + _evaluateConstant('a', declaredVariables: {'a': '42'}), + ''' +bool true +''', + ); + } + test_dotShorthand_constantArgument_issue60963() async { await assertNoErrorsInCode(''' class A {
diff --git a/pkg/analyzer/test/src/summary/elements/const_test.dart b/pkg/analyzer/test/src/summary/elements/const_test.dart index aaf2a87..5e5396b 100644 --- a/pkg/analyzer/test/src/summary/elements/const_test.dart +++ b/pkg/analyzer/test/src/summary/elements/const_test.dart
@@ -725,6 +725,80 @@ '''); } + test_const_dotShorthand_invalid_methodInvocation() async { + var library = await buildLibrary(r''' +class A { + static A method() => A(); +} + +const A a = .method(); +'''); + checkElementText(library, r''' +library + reference: <testLibrary> + fragments + #F0 <testLibraryFragment> + element: <testLibrary> + classes + #F1 class A @6 + element: <testLibrary>::@class::A + constructors + #F2 synthetic new + element: <testLibrary>::@class::A::@constructor::new + typeName: A + methods + #F3 method @21 + element: <testLibrary>::@class::A::@method::method + topLevelVariables + #F4 hasInitializer a @49 + element: <testLibrary>::@topLevelVariable::a + initializer: expression_0 + DotShorthandInvocation + period: . @53 + memberName: SimpleIdentifier + token: method @54 + element: <testLibrary>::@class::A::@method::method + staticType: A Function() + argumentList: ArgumentList + leftParenthesis: ( @60 + rightParenthesis: ) @61 + staticInvokeType: A Function() + staticType: A + getters + #F5 synthetic a + element: <testLibrary>::@getter::a + returnType: A + classes + class A + reference: <testLibrary>::@class::A + firstFragment: #F1 + constructors + synthetic new + reference: <testLibrary>::@class::A::@constructor::new + firstFragment: #F2 + methods + static method + reference: <testLibrary>::@class::A::@method::method + firstFragment: #F3 + returnType: A + topLevelVariables + const hasInitializer a + reference: <testLibrary>::@topLevelVariable::a + firstFragment: #F4 + type: A + constantInitializer + fragment: #F4 + expression: expression_0 + getter: <testLibrary>::@getter::a + getters + synthetic static a + reference: <testLibrary>::@getter::a + firstFragment: #F5 + returnType: A + variable: <testLibrary>::@topLevelVariable::a +'''); + } + test_const_dotShorthand_property() async { var library = await buildLibrary(r''' class A {