[messages] Convert assignability checks to use literate diagnostic API.
Converts the code that reports diagnostic codes `invalidAssignment` and `argumentTypeNotAssignable` to use the literate diagnostic reporting API. This was challenging because some of the code for reporting these diagnostics (specifically the method `ErrorDetectionHelpers.checkForAssignableExpressionAtType`) was parameterized by diagnostic code, but the two diagnostics accepted a different number of parameters. To get the type safety of the literate diagnostic API while preserving the existing behavior, I've created an abstract base class, `NonAssignabilityReporter`, with a method `createDiagnostic`, that encapsulates the differences in how to handle the two diagnostic codes. Change-Id: I6a6a69649e53f6980b5033771b14dbfdaa5895d6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467481 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
@@ -155,14 +155,19 @@ class AssignmentExpressionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
_diagnosticReporter.atNode(
|
||||
right,
|
||||
diag.invalidAssignment,
|
||||
arguments: [rightType, writeType],
|
||||
contextMessages: _resolver.computeWhyNotPromotedMessages(
|
||||
right,
|
||||
whyNotPromoted?.call(),
|
||||
),
|
||||
_diagnosticReporter.report(
|
||||
diag.invalidAssignment
|
||||
.withArguments(
|
||||
actualStaticType: rightType,
|
||||
expectedStaticType: writeType,
|
||||
)
|
||||
.withContextMessages(
|
||||
_resolver.computeWhyNotPromotedMessages(
|
||||
right,
|
||||
whyNotPromoted?.call(),
|
||||
),
|
||||
)
|
||||
.at(right),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,13 @@ class PostfixExpressionResolver {
|
||||
operandWriteType,
|
||||
strictCasts: _resolver.analysisOptions.strictCasts,
|
||||
)) {
|
||||
_resolver.diagnosticReporter.atNode(
|
||||
node,
|
||||
diag.invalidAssignment,
|
||||
arguments: [type, operandWriteType],
|
||||
_resolver.diagnosticReporter.report(
|
||||
diag.invalidAssignment
|
||||
.withArguments(
|
||||
actualStaticType: type,
|
||||
expectedStaticType: operandWriteType,
|
||||
)
|
||||
.at(node),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,13 @@ class PrefixExpressionResolver {
|
||||
operandWriteType,
|
||||
strictCasts: _resolver.analysisOptions.strictCasts,
|
||||
)) {
|
||||
_resolver.diagnosticReporter.atNode(
|
||||
node,
|
||||
diag.invalidAssignment,
|
||||
arguments: [type, operandWriteType],
|
||||
_resolver.diagnosticReporter.report(
|
||||
diag.invalidAssignment
|
||||
.withArguments(
|
||||
actualStaticType: type,
|
||||
expectedStaticType: operandWriteType,
|
||||
)
|
||||
.at(node),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:analyzer/src/dart/ast/extensions.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/element/type_schema.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
|
||||
import 'package:analyzer/src/generated/error_detection_helpers.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart';
|
||||
|
||||
/// Helper for resolving [VariableDeclaration]s.
|
||||
@@ -84,7 +85,7 @@ class VariableDeclarationResolver {
|
||||
initializer,
|
||||
initializerType,
|
||||
element.type,
|
||||
diag.invalidAssignment,
|
||||
const NonAssignabilityReporterForAssignment(),
|
||||
whyNotPromoted: whyNotPromoted,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -506,15 +506,15 @@ const DiagnosticWithoutArguments argumentMustBeNative =
|
||||
);
|
||||
|
||||
/// Parameters:
|
||||
/// Type p0: the name of the actual argument type
|
||||
/// Type p1: the name of the expected type
|
||||
/// String p2: additional information, if any, when problem is associated with
|
||||
/// records
|
||||
/// Type actualStaticType: the name of the actual argument type
|
||||
/// Type expectedStaticType: the name of the expected type
|
||||
/// String additionalInfo: additional information, if any, when problem is
|
||||
/// associated with records
|
||||
const DiagnosticWithArguments<
|
||||
LocatableDiagnostic Function({
|
||||
required DartType p0,
|
||||
required DartType p1,
|
||||
required String p2,
|
||||
required DartType actualStaticType,
|
||||
required DartType expectedStaticType,
|
||||
required String additionalInfo,
|
||||
})
|
||||
>
|
||||
argumentTypeNotAssignable = DiagnosticWithArguments(
|
||||
@@ -7569,10 +7569,13 @@ invalidAnnotationTarget = DiagnosticWithArguments(
|
||||
);
|
||||
|
||||
/// Parameters:
|
||||
/// Type p0: the name of the right hand side type
|
||||
/// Type p1: the name of the left hand side type
|
||||
/// Type actualStaticType: the name of the right hand side type
|
||||
/// Type expectedStaticType: the name of the left hand side type
|
||||
const DiagnosticWithArguments<
|
||||
LocatableDiagnostic Function({required DartType p0, required DartType p1})
|
||||
LocatableDiagnostic Function({
|
||||
required DartType actualStaticType,
|
||||
required DartType expectedStaticType,
|
||||
})
|
||||
>
|
||||
invalidAssignment = DiagnosticWithArguments(
|
||||
name: 'invalid_assignment',
|
||||
@@ -17799,11 +17802,15 @@ LocatableDiagnostic _withArgumentsArgumentMustBeAConstant({
|
||||
}
|
||||
|
||||
LocatableDiagnostic _withArgumentsArgumentTypeNotAssignable({
|
||||
required DartType p0,
|
||||
required DartType p1,
|
||||
required String p2,
|
||||
required DartType actualStaticType,
|
||||
required DartType expectedStaticType,
|
||||
required String additionalInfo,
|
||||
}) {
|
||||
return LocatableDiagnosticImpl(diag.argumentTypeNotAssignable, [p0, p1, p2]);
|
||||
return LocatableDiagnosticImpl(diag.argumentTypeNotAssignable, [
|
||||
actualStaticType,
|
||||
expectedStaticType,
|
||||
additionalInfo,
|
||||
]);
|
||||
}
|
||||
|
||||
LocatableDiagnostic _withArgumentsArgumentTypeNotAssignableToErrorHandler({
|
||||
@@ -19242,10 +19249,13 @@ LocatableDiagnostic _withArgumentsInvalidAnnotationTarget({
|
||||
}
|
||||
|
||||
LocatableDiagnostic _withArgumentsInvalidAssignment({
|
||||
required DartType p0,
|
||||
required DartType p1,
|
||||
required DartType actualStaticType,
|
||||
required DartType expectedStaticType,
|
||||
}) {
|
||||
return LocatableDiagnosticImpl(diag.invalidAssignment, [p0, p1]);
|
||||
return LocatableDiagnosticImpl(diag.invalidAssignment, [
|
||||
actualStaticType,
|
||||
expectedStaticType,
|
||||
]);
|
||||
}
|
||||
|
||||
LocatableDiagnostic _withArgumentsInvalidCastFunction({
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/diagnostic/diagnostic.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
import 'package:analyzer/src/dart/ast/extensions.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
@@ -20,6 +19,7 @@ import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/element/type_system.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/error/listener.dart';
|
||||
import 'package:analyzer/src/utilities/extensions/object.dart';
|
||||
|
||||
@@ -43,7 +43,7 @@ mixin ErrorDetectionHelpers {
|
||||
Expression expression,
|
||||
TypeImpl expectedStaticType,
|
||||
TypeImpl actualStaticType,
|
||||
DiagnosticCode diagnosticCode, {
|
||||
NonAssignabilityReporter nonAssignabilityReporter, {
|
||||
Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted,
|
||||
}) {
|
||||
if (expectedStaticType is! VoidType &&
|
||||
@@ -55,7 +55,7 @@ mixin ErrorDetectionHelpers {
|
||||
expression,
|
||||
actualStaticType,
|
||||
expectedStaticType,
|
||||
diagnosticCode,
|
||||
nonAssignabilityReporter,
|
||||
whyNotPromoted: whyNotPromoted,
|
||||
);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ mixin ErrorDetectionHelpers {
|
||||
Expression expression,
|
||||
TypeImpl actualStaticType,
|
||||
TypeImpl expectedStaticType,
|
||||
DiagnosticCode diagnosticCode, {
|
||||
NonAssignabilityReporter nonAssignabilityReporter, {
|
||||
Map<SharedTypeView, NonPromotionReason> Function()? whyNotPromoted,
|
||||
}) {
|
||||
if (expectedStaticType is! VoidType &&
|
||||
@@ -142,64 +142,18 @@ mixin ErrorDetectionHelpers {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (diagnosticCode == diag.argumentTypeNotAssignable) {
|
||||
var additionalInfo = <String>[];
|
||||
if (expectedStaticType is RecordTypeImpl &&
|
||||
actualStaticType is RecordTypeImpl) {
|
||||
var actualPositionalFields = actualStaticType.positionalFields.length;
|
||||
var expectedPositionalFields =
|
||||
expectedStaticType.positionalFields.length;
|
||||
if (expectedPositionalFields != 0 &&
|
||||
actualPositionalFields != expectedPositionalFields) {
|
||||
additionalInfo.add(
|
||||
'Expected $expectedPositionalFields positional arguments, but got $actualPositionalFields instead.',
|
||||
);
|
||||
}
|
||||
var actualNamedFieldsLength = actualStaticType.namedFields.length;
|
||||
var expectedNamedFieldsLength = expectedStaticType.namedFields.length;
|
||||
if (expectedNamedFieldsLength != 0 &&
|
||||
actualNamedFieldsLength != expectedNamedFieldsLength) {
|
||||
additionalInfo.add(
|
||||
'Expected $expectedNamedFieldsLength named arguments, but got $actualNamedFieldsLength instead.',
|
||||
);
|
||||
}
|
||||
var namedFields = expectedStaticType.namedFields;
|
||||
if (namedFields.isNotEmpty) {
|
||||
for (var field in actualStaticType.namedFields) {
|
||||
if (!namedFields.any(
|
||||
(element) =>
|
||||
element.name == field.name && field.type == element.type,
|
||||
)) {
|
||||
additionalInfo.add(
|
||||
'Unexpected named argument `${field.name}` with type `${field.type.getDisplayString()}`.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diagnosticReporter.atNode(
|
||||
getErrorNode(expression),
|
||||
diagnosticCode,
|
||||
arguments: [
|
||||
actualStaticType,
|
||||
expectedStaticType,
|
||||
additionalInfo.join(' '),
|
||||
],
|
||||
contextMessages: computeWhyNotPromotedMessages(
|
||||
expression,
|
||||
whyNotPromoted?.call(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
diagnosticReporter.atNode(
|
||||
getErrorNode(expression),
|
||||
diagnosticCode,
|
||||
arguments: [actualStaticType, expectedStaticType],
|
||||
contextMessages: computeWhyNotPromotedMessages(
|
||||
expression,
|
||||
whyNotPromoted?.call(),
|
||||
),
|
||||
var whyNotPromotedMessages = computeWhyNotPromotedMessages(
|
||||
expression,
|
||||
whyNotPromoted?.call(),
|
||||
);
|
||||
diagnosticReporter.report(
|
||||
nonAssignabilityReporter
|
||||
.createDiagnostic(
|
||||
expectedStaticType: expectedStaticType,
|
||||
actualStaticType: actualStaticType,
|
||||
)
|
||||
.withContextMessages(whyNotPromotedMessages)
|
||||
.at(getErrorNode(expression)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -413,8 +367,89 @@ mixin ErrorDetectionHelpers {
|
||||
argument,
|
||||
staticParameterType,
|
||||
argument.typeOrThrow,
|
||||
diag.argumentTypeNotAssignable,
|
||||
const NonAssignabilityReporterForArgument(),
|
||||
whyNotPromoted: whyNotPromoted,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract helper class for reporting an error related to non-assignability.
|
||||
///
|
||||
/// Concrete derived classes exist for handling the different varieties of
|
||||
/// non-assignability errors.
|
||||
abstract class NonAssignabilityReporter {
|
||||
const NonAssignabilityReporter();
|
||||
|
||||
/// Creates the appropriate [LocatableDiagnostic] to report that the user has
|
||||
/// tried to assign [actualStaticType] to [expectedStaticType].
|
||||
LocatableDiagnostic createDiagnostic({
|
||||
required TypeImpl expectedStaticType,
|
||||
required TypeImpl actualStaticType,
|
||||
});
|
||||
}
|
||||
|
||||
/// Helper class for reporting the error [diag.argumentTypeNotAssignable].
|
||||
class NonAssignabilityReporterForArgument extends NonAssignabilityReporter {
|
||||
const NonAssignabilityReporterForArgument();
|
||||
|
||||
@override
|
||||
LocatableDiagnostic createDiagnostic({
|
||||
required TypeImpl expectedStaticType,
|
||||
required TypeImpl actualStaticType,
|
||||
}) {
|
||||
var additionalInfo = <String>[];
|
||||
if (expectedStaticType is RecordTypeImpl &&
|
||||
actualStaticType is RecordTypeImpl) {
|
||||
var actualPositionalFields = actualStaticType.positionalFields.length;
|
||||
var expectedPositionalFields = expectedStaticType.positionalFields.length;
|
||||
if (expectedPositionalFields != 0 &&
|
||||
actualPositionalFields != expectedPositionalFields) {
|
||||
additionalInfo.add(
|
||||
'Expected $expectedPositionalFields positional arguments, but got $actualPositionalFields instead.',
|
||||
);
|
||||
}
|
||||
var actualNamedFieldsLength = actualStaticType.namedFields.length;
|
||||
var expectedNamedFieldsLength = expectedStaticType.namedFields.length;
|
||||
if (expectedNamedFieldsLength != 0 &&
|
||||
actualNamedFieldsLength != expectedNamedFieldsLength) {
|
||||
additionalInfo.add(
|
||||
'Expected $expectedNamedFieldsLength named arguments, but got $actualNamedFieldsLength instead.',
|
||||
);
|
||||
}
|
||||
var namedFields = expectedStaticType.namedFields;
|
||||
if (namedFields.isNotEmpty) {
|
||||
for (var field in actualStaticType.namedFields) {
|
||||
if (!namedFields.any(
|
||||
(element) =>
|
||||
element.name == field.name && field.type == element.type,
|
||||
)) {
|
||||
additionalInfo.add(
|
||||
'Unexpected named argument `${field.name}` with type `${field.type.getDisplayString()}`.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return diag.argumentTypeNotAssignable.withArguments(
|
||||
actualStaticType: actualStaticType,
|
||||
expectedStaticType: expectedStaticType,
|
||||
additionalInfo: additionalInfo.join(' '),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper class for reporting the error [diag.invalidAssignment].
|
||||
class NonAssignabilityReporterForAssignment extends NonAssignabilityReporter {
|
||||
const NonAssignabilityReporterForAssignment();
|
||||
|
||||
@override
|
||||
LocatableDiagnostic createDiagnostic({
|
||||
required TypeImpl expectedStaticType,
|
||||
required TypeImpl actualStaticType,
|
||||
}) {
|
||||
return diag.invalidAssignment.withArguments(
|
||||
actualStaticType: actualStaticType,
|
||||
expectedStaticType: expectedStaticType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,7 +684,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
|
||||
defaultValue,
|
||||
defaultValue.typeOrThrow,
|
||||
node.declaredFragment!.element.type,
|
||||
diag.invalidAssignment,
|
||||
const NonAssignabilityReporterForAssignment(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3754,7 +3754,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
|
||||
argument,
|
||||
parameterType,
|
||||
_intType,
|
||||
diag.argumentTypeNotAssignable,
|
||||
const NonAssignabilityReporterForArgument(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1331,10 +1331,10 @@ CompileTimeErrorCode:
|
||||
argumentTypeNotAssignable:
|
||||
type: compileTimeError
|
||||
parameters:
|
||||
Type p0: the name of the actual argument type
|
||||
Type p1: the name of the expected type
|
||||
String p2: additional information, if any, when problem is associated with records
|
||||
problemMessage: "The argument type '#p0' can't be assigned to the parameter type '#p1'. #p2"
|
||||
Type actualStaticType: the name of the actual argument type
|
||||
Type expectedStaticType: the name of the expected type
|
||||
String additionalInfo: additional information, if any, when problem is associated with records
|
||||
problemMessage: "The argument type '#actualStaticType' can't be assigned to the parameter type '#expectedStaticType'. #additionalInfo"
|
||||
hasPublishedDocs: true
|
||||
documentation: |-
|
||||
#### Description
|
||||
@@ -9287,10 +9287,10 @@ CompileTimeErrorCode:
|
||||
invalidAssignment:
|
||||
type: compileTimeError
|
||||
parameters:
|
||||
Type p0: the name of the right hand side type
|
||||
Type p1: the name of the left hand side type
|
||||
problemMessage: "A value of type '#p0' can't be assigned to a variable of type '#p1'."
|
||||
correctionMessage: "Try changing the type of the variable, or casting the right-hand type to '#p1'."
|
||||
Type actualStaticType: the name of the right hand side type
|
||||
Type expectedStaticType: the name of the left hand side type
|
||||
problemMessage: "A value of type '#actualStaticType' can't be assigned to a variable of type '#expectedStaticType'."
|
||||
correctionMessage: "Try changing the type of the variable, or casting the right-hand type to '#expectedStaticType'."
|
||||
hasPublishedDocs: true
|
||||
documentation: |-
|
||||
#### Description
|
||||
|
||||
@@ -85,10 +85,14 @@ main() {
|
||||
firstType.element.firstFragment.libraryFragment.source,
|
||||
);
|
||||
|
||||
reporter.atNode(
|
||||
findNode.simple('x'),
|
||||
diag.argumentTypeNotAssignable,
|
||||
arguments: [firstType, secondType, ''],
|
||||
reporter.report(
|
||||
diag.argumentTypeNotAssignable
|
||||
.withArguments(
|
||||
actualStaticType: firstType,
|
||||
expectedStaticType: secondType,
|
||||
additionalInfo: '',
|
||||
)
|
||||
.at(findNode.simple('x')),
|
||||
);
|
||||
|
||||
var diagnostic = listener.diagnostics[0];
|
||||
@@ -126,10 +130,14 @@ main() {
|
||||
listener,
|
||||
firstType.element.firstFragment.libraryFragment.source,
|
||||
);
|
||||
reporter.atNode(
|
||||
findNode.simple('x'),
|
||||
diag.argumentTypeNotAssignable,
|
||||
arguments: [firstType, secondType, ''],
|
||||
reporter.report(
|
||||
diag.argumentTypeNotAssignable
|
||||
.withArguments(
|
||||
actualStaticType: firstType,
|
||||
expectedStaticType: secondType,
|
||||
additionalInfo: '',
|
||||
)
|
||||
.at(findNode.simple('x')),
|
||||
);
|
||||
|
||||
var diagnostic = listener.diagnostics[0];
|
||||
@@ -155,10 +163,14 @@ main() {
|
||||
|
||||
var source = result.unit.declaredFragment!.source;
|
||||
var reporter = DiagnosticReporter(listener, source);
|
||||
reporter.atNode(
|
||||
findNode.simple('x'),
|
||||
diag.argumentTypeNotAssignable,
|
||||
arguments: [fa.variables.type!.type!, fb.variables.type!.type!, ''],
|
||||
reporter.report(
|
||||
diag.argumentTypeNotAssignable
|
||||
.withArguments(
|
||||
actualStaticType: fa.variables.type!.type!,
|
||||
expectedStaticType: fb.variables.type!.type!,
|
||||
additionalInfo: '',
|
||||
)
|
||||
.at(findNode.simple('x')),
|
||||
);
|
||||
|
||||
var diagnostic = listener.diagnostics[0];
|
||||
@@ -186,10 +198,14 @@ main() {
|
||||
|
||||
var source = result.unit.declaredFragment!.source;
|
||||
var reporter = DiagnosticReporter(listener, source);
|
||||
reporter.atNode(
|
||||
findNode.simple('x'),
|
||||
diag.argumentTypeNotAssignable,
|
||||
arguments: [ba.variables.type!.type!, bb.variables.type!.type!, ''],
|
||||
reporter.report(
|
||||
diag.argumentTypeNotAssignable
|
||||
.withArguments(
|
||||
actualStaticType: ba.variables.type!.type!,
|
||||
expectedStaticType: bb.variables.type!.type!,
|
||||
additionalInfo: '',
|
||||
)
|
||||
.at(findNode.simple('x')),
|
||||
);
|
||||
|
||||
var diagnostic = listener.diagnostics[0];
|
||||
|
||||
Reference in New Issue
Block a user