// Copyright (c) 2021, 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. import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/src/dart/error/syntactic_errors.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import '../util/feature_sets.dart'; import 'parser_test_base.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(FunctionReferenceParserTest); }); } /// Tests exercising the fasta parser's handling of generic instantiations. @reflectiveTest class FunctionReferenceParserTest extends FastaParserTestCase { /// Verifies that the given [node] matches `f`. void expect_f_a_b(AstNode node) { var functionReference = node as FunctionReference; expect((functionReference.function as SimpleIdentifier).name, 'f'); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void expect_two_args(MethodInvocation methodInvocation) { var arguments = methodInvocation.argumentList.arguments; expect(arguments, hasLength(2)); expect(arguments[0], TypeMatcher()); expect(arguments[1], TypeMatcher()); } void test_feature_disabled() { expect_f_a_b( (parseStatement('f;', featureSet: FeatureSets.language_2_13) as ExpressionStatement) .expression); listener.assertErrors([ expectedError(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 1, 6), ]); } void test_followingToken_accepted_closeBrace() { expect_f_a_b((parseExpression('{f}') as SetOrMapLiteral).elements[0]); } void test_followingToken_accepted_closeBracket() { expect_f_a_b((parseExpression('[f]') as ListLiteral).elements[0]); } void test_followingToken_accepted_closeParen() { expect_f_a_b((parseExpression('g(f)') as MethodInvocation) .argumentList .arguments[0]); } void test_followingToken_accepted_colon() { expect_f_a_b(((parseExpression('{f: null}') as SetOrMapLiteral) .elements[0] as MapLiteralEntry) .key); } void test_followingToken_accepted_comma() { expect_f_a_b( (parseExpression('[f, null]') as ListLiteral).elements[0]); } void test_followingToken_accepted_equals() { expect_f_a_b( (parseExpression('f == null') as BinaryExpression).leftOperand); } void test_followingToken_accepted_not_equals() { expect_f_a_b( (parseExpression('f != null') as BinaryExpression).leftOperand); } void test_followingToken_accepted_openParen() { // This is a special case because when a `(` follows `` it is // parsed as a MethodInvocation rather than a GenericInstantiation. var methodInvocation = parseExpression('f()') as MethodInvocation; expect(methodInvocation.methodName.name, 'f'); var typeArgs = methodInvocation.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); expect(methodInvocation.argumentList.arguments, isEmpty); } void test_followingToken_accepted_period_methodInvocation() { // This is a special case because `f.methodName(...)` is parsed as an // InstanceCreationExpression. var instanceCreationExpression = parseExpression('f.toString()') as InstanceCreationExpression; var constructorName = instanceCreationExpression.constructorName; var type = constructorName.type; expect(type.name2.lexeme, 'f'); var typeArgs = type.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); expect(constructorName.name!.name, 'toString'); expect(instanceCreationExpression.argumentList.arguments, isEmpty); } void test_followingToken_accepted_period_methodInvocation_generic() { expect_f_a_b( (parseExpression('f.foo()') as MethodInvocation).target!); } void test_followingToken_accepted_period_propertyAccess() { expect_f_a_b( (parseExpression('f.hashCode') as PropertyAccess).target!); } void test_followingToken_accepted_semicolon() { expect_f_a_b( (parseStatement('f;') as ExpressionStatement).expression); listener.assertNoErrors(); } void test_followingToken_rejected_ampersand() { expect_two_args(parseExpression('f(a&d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_as() { expect_two_args(parseExpression('f(aas)') as MethodInvocation); } void test_followingToken_rejected_asterisk() { expect_two_args(parseExpression('f(a*d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_bang_openBracket() { expect_two_args(parseExpression('f(a![d])') as MethodInvocation); } void test_followingToken_rejected_bang_paren() { expect_two_args(parseExpression('f(a!(d))') as MethodInvocation); } void test_followingToken_rejected_bar() { expect_two_args(parseExpression('f(a|d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_caret() { expect_two_args(parseExpression('f(a^d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_is() { var methodInvocation = parseExpression('f(a is int)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 9, 2), ]) as MethodInvocation; var arguments = methodInvocation.argumentList.arguments; expect(arguments, hasLength(2)); expect(arguments[0], TypeMatcher()); expect(arguments[1], TypeMatcher()); } void test_followingToken_rejected_lessThan() { // Note: in principle we could parse this as a generic instantiation of a // generic instantiation, but such an expression would be meaningless so we // reject it at the parser level. parseExpression('f', errors: [ expectedError(ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND, 3, 1), expectedError(ParserErrorCode.EXPECTED_TOKEN, 7, 0), ]); } void test_followingToken_rejected_minus() { expect_two_args(parseExpression('f(a-d)') as MethodInvocation); } void test_followingToken_rejected_openBracket() { expect_two_args(parseExpression('f(a[d])') as MethodInvocation); } void test_followingToken_rejected_openBracket_error() { // Note that theoretically this could be successfully parsed by interpreting // `<` and `>` as delimiting type arguments, but the parser doesn't have // enough lookahead to see that this is the only possible error-free parse; // it commits to interpreting `<` and `>` as operators when it sees the `[`. expect_two_args(parseExpression('f(a[d]>e)', errors: [ expectedError(ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND, 11, 1), ]) as MethodInvocation); } void test_followingToken_rejected_openBracket_unambiguous() { expect_two_args(parseExpression('f(a[d, e])') as MethodInvocation); } void test_followingToken_rejected_percent() { expect_two_args(parseExpression('f(a%d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_period_period() { var methodInvocation = parseExpression('f(a..toString())', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 2), ]) as MethodInvocation; var arguments = methodInvocation.argumentList.arguments; expect(arguments, hasLength(2)); expect(arguments[0], TypeMatcher()); expect(arguments[1], TypeMatcher()); } void test_followingToken_rejected_plus() { expect_two_args(parseExpression('f(a+d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_question() { var methodInvocation = parseExpression('f(a ? null : null)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 9, 1), ]) as MethodInvocation; var arguments = methodInvocation.argumentList.arguments; expect(arguments, hasLength(2)); expect(arguments[0], TypeMatcher()); expect(arguments[1], TypeMatcher()); } void test_followingToken_rejected_question_period_methodInvocation() { expect_two_args(parseExpression('f(a?.toString())', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 2), ]) as MethodInvocation); } void test_followingToken_rejected_question_period_methodInvocation_generic() { expect_two_args(parseExpression('f(a?.foo())', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 2), ]) as MethodInvocation); } void test_followingToken_rejected_question_period_period() { var methodInvocation = parseExpression('f(a?..toString())', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 3), expectedError(ParserErrorCode.EXPECTED_TOKEN, 11, 8), ]) as MethodInvocation; var arguments = methodInvocation.argumentList.arguments; expect(arguments, hasLength(3)); expect(arguments[0], TypeMatcher()); expect(arguments[1], TypeMatcher()); expect(arguments[2], TypeMatcher()); } void test_followingToken_rejected_question_period_propertyAccess() { expect_two_args(parseExpression('f(a?.hashCode)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 2), ]) as MethodInvocation); } void test_followingToken_rejected_question_question() { expect_two_args(parseExpression('f(a ?? d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 9, 2), ]) as MethodInvocation); } void test_followingToken_rejected_slash() { expect_two_args(parseExpression('f(a/d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 1), ]) as MethodInvocation); } void test_followingToken_rejected_tilde_slash() { expect_two_args(parseExpression('f(a~/d)', errors: [ expectedError(ParserErrorCode.MISSING_IDENTIFIER, 8, 2), ]) as MethodInvocation); } void test_functionReference_after_indexExpression() { // Note: this is not legal Dart, but it's important that we do error // recovery and don't crash the parser. var functionReference = parseExpression('x[0]') as FunctionReference; expect(functionReference.function, TypeMatcher()); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_functionReference_after_indexExpression_bang() { // Note: this is not legal Dart, but it's important that we do error // recovery and don't crash the parser. var functionReference = parseExpression('x[0]!') as FunctionReference; expect(functionReference.function, TypeMatcher()); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_functionReference_after_indexExpression_functionCall() { // Note: this is not legal Dart, but it's important that we do error // recovery and don't crash the parser. var functionReference = parseExpression('x[0]()') as FunctionReference; expect(functionReference.function, TypeMatcher()); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_functionReference_after_indexExpression_nullAware() { // Note: this is not legal Dart, but it's important that we do error // recovery and don't crash the parser. var functionReference = parseExpression('x?[0]') as FunctionReference; expect(functionReference.function, TypeMatcher()); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_methodTearoff() { var functionReference = parseExpression('f().m') as FunctionReference; var function = functionReference.function as PropertyAccess; var target = function.target as MethodInvocation; expect(target.methodName.name, 'f'); expect(function.propertyName.name, 'm'); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_methodTearoff_cascaded() { var cascadeExpression = parseExpression('f()..m') as CascadeExpression; var functionReference = cascadeExpression.cascadeSections[0] as FunctionReference; var function = functionReference.function as PropertyAccess; expect(function.target, isNull); expect(function.propertyName.name, 'm'); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_prefixedIdentifier() { var functionReference = parseExpression('prefix.f') as FunctionReference; var function = functionReference.function as PrefixedIdentifier; expect(function.prefix.name, 'prefix'); expect(function.identifier.name, 'f'); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } void test_three_identifiers() { var functionReference = parseExpression('prefix.ClassName.m') as FunctionReference; var function = functionReference.function as PropertyAccess; var target = function.target as PrefixedIdentifier; expect(target.prefix.name, 'prefix'); expect(target.identifier.name, 'ClassName'); expect(function.propertyName.name, 'm'); var typeArgs = functionReference.typeArguments!.arguments; expect(typeArgs, hasLength(2)); expect((typeArgs[0] as NamedType).name2.lexeme, 'a'); expect((typeArgs[1] as NamedType).name2.lexeme, 'b'); } }