blob: fcda4a3ae1813bb666240cc29b6c290c5222f013 [file] [log] [blame] [edit]
// Copyright (c) 2025, 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:kernel/ast.dart' hide Combinator, MapLiteralEntry;
import 'package:kernel/class_hierarchy.dart'
show ClassHierarchyBase, ClassHierarchyMembers;
import 'package:kernel/src/bounds_checks.dart'
show
TypeArgumentIssue,
findTypeArgumentIssues,
findTypeArgumentIssuesForInvocation,
getGenericTypeName,
hasGenericFunctionTypeAsTypeArgument;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart' show TypeEnvironment;
import '../api_prototype/experimental_flags.dart';
import '../base/compiler_context.dart';
import '../base/messages.dart';
import '../base/uri_offset.dart';
import '../builder/formal_parameter_builder.dart';
import '../kernel/internal_ast.dart';
import 'source_library_builder.dart';
extension CheckHelper on ProblemReporting {
InvalidExpression buildProblem({
required CompilerContext compilerContext,
required Message message,
required Uri fileUri,
required int fileOffset,
required int length,
List<LocatedMessage>? context,
bool errorHasBeenReported = false,
Expression? expression,
}) {
if (!errorHasBeenReported) {
addProblem(
message,
fileOffset,
length,
fileUri,
wasHandled: true,
context: context,
);
}
String text = compilerContext
.format(
message.withLocation(fileUri, fileOffset, length),
CfeSeverity.error,
)
.plain;
return new InvalidExpression(text, expression)..fileOffset = fileOffset;
}
Expression buildProblemWithContextFromMember({
required CompilerContext compilerContext,
required String name,
required Member member,
required LocatedMessage message,
required Uri fileUri,
}) {
List<LocatedMessage>? context;
Location? location = member.location;
if (location != null) {
Uri uri = location.file;
int offset = member.fileOffset;
Message contextMessage;
int length = noLength;
if (member is Constructor && member.isSynthetic) {
offset = member.enclosingClass.fileOffset;
contextMessage = codeCandidateFoundIsDefaultConstructor
.withArgumentsOld(member.enclosingClass.name);
} else {
if (member is Constructor) {
if (member.name.text == '') {
length = member.enclosingClass.name.length;
} else {
// Assume no spaces around the dot. Not perfect, but probably the
// best we can do with the information available.
length = member.enclosingClass.name.length + 1 + name.length;
}
} else {
length = name.length;
}
contextMessage = codeCandidateFound;
}
context = [contextMessage.withLocation(uri, offset, length)];
}
return buildProblem(
compilerContext: compilerContext,
message: message.messageObject,
fileUri: fileUri,
fileOffset: message.charOffset,
length: message.length,
context: context,
);
}
Expression buildProblemFromLocatedMessage({
required CompilerContext compilerContext,
required LocatedMessage message,
}) {
return buildProblem(
compilerContext: compilerContext,
message: message.messageObject,
fileUri: message.uri!,
fileOffset: message.charOffset,
length: message.length,
);
}
LocatedMessage? checkArgumentsForFunction({
required FunctionNode function,
required Arguments arguments,
required int fileOffset,
required Uri fileUri,
required List<TypeParameter> typeParameters,
Extension? extension,
}) {
int typeParameterCount = typeParameters.length;
int requiredParameterCount = function.requiredParameterCount;
int positionalParameterCount = function.positionalParameters.length;
int positionalArgumentsCount = arguments.positional.length;
if (extension != null) {
// Extension member invocations have additional synthetic parameter for
// `this`.
--requiredParameterCount;
--positionalParameterCount;
typeParameterCount -= extension.typeParameters.length;
}
if (positionalArgumentsCount < requiredParameterCount) {
return codeTooFewArguments
.withArgumentsOld(requiredParameterCount, positionalArgumentsCount)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
if (positionalArgumentsCount > positionalParameterCount) {
return codeTooManyArguments
.withArgumentsOld(positionalParameterCount, positionalArgumentsCount)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
List<NamedExpression> named = arguments.named;
if (named.isNotEmpty) {
Set<String?> parameterNames = new Set.of(
function.namedParameters.map((a) => a.name),
);
for (int i = 0; i < named.length; i++) {
NamedExpression argument = named[i];
if (!parameterNames.contains(argument.name)) {
return codeNoSuchNamedParameter
.withArgumentsOld(argument.name)
.withLocation(fileUri, argument.fileOffset, argument.name.length);
}
}
}
if (function.namedParameters.isNotEmpty) {
Set<String> argumentNames = new Set.of(named.map((a) => a.name));
for (int i = 0; i < function.namedParameters.length; i++) {
VariableDeclaration parameter = function.namedParameters[i];
if (parameter.isRequired && !argumentNames.contains(parameter.name)) {
return codeValueForRequiredParameterNotProvidedError
.withArgumentsOld(parameter.name!)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
}
}
List<DartType> types = arguments.types;
if (typeParameterCount != types.length) {
if (types.length == 0) {
// Expected `typeParameters.length` type arguments, but none given, so
// we use type inference.
} else {
// A wrong (non-zero) amount of type arguments given. That's an error.
// TODO(jensj): Position should be on type arguments instead.
return codeTypeArgumentMismatch
.withArgumentsOld(typeParameterCount)
.withLocation(fileUri, fileOffset, noLength);
}
}
return null;
}
LocatedMessage? checkArgumentsForType({
required FunctionType function,
required Arguments arguments,
required Uri fileUri,
required int fileOffset,
}) {
int requiredPositionalParameterCountToReport =
function.requiredParameterCount;
int positionalParameterCountToReport = function.positionalParameters.length;
int positionalArgumentCountToReport = arguments.positional.length;
if (arguments.positional.length < function.requiredParameterCount) {
return codeTooFewArguments
.withArgumentsOld(
requiredPositionalParameterCountToReport,
positionalArgumentCountToReport,
)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
if (arguments.positional.length > function.positionalParameters.length) {
return codeTooManyArguments
.withArgumentsOld(
positionalParameterCountToReport,
positionalArgumentCountToReport,
)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
List<NamedExpression> named = arguments.named;
if (named.isNotEmpty) {
Set<String> names = new Set.of(
function.namedParameters.map((a) => a.name),
);
for (int i = 0; i < named.length; i++) {
NamedExpression argument = named[i];
if (!names.contains(argument.name)) {
return codeNoSuchNamedParameter
.withArgumentsOld(argument.name)
.withLocation(fileUri, argument.fileOffset, argument.name.length);
}
}
}
if (function.namedParameters.isNotEmpty) {
Set<String> argumentNames = new Set.of(named.map((a) => a.name));
for (int i = 0; i < function.namedParameters.length; i++) {
NamedType parameter = function.namedParameters[i];
if (parameter.isRequired && !argumentNames.contains(parameter.name)) {
return codeValueForRequiredParameterNotProvidedError
.withArgumentsOld(parameter.name)
.withLocation(fileUri, arguments.fileOffset, noLength);
}
}
}
List<Object> types = arguments.types;
List<StructuralParameter> typeParameters = function.typeParameters;
if (typeParameters.length != types.length && types.length != 0) {
// A wrong (non-zero) amount of type arguments given. That's an error.
// TODO(jensj): Position should be on type arguments instead.
return codeTypeArgumentMismatch
.withArgumentsOld(typeParameters.length)
.withLocation(fileUri, fileOffset, noLength);
}
return null;
}
void checkAsyncReturnType({
required SourceLibraryBuilder libraryBuilder,
required TypeEnvironment typeEnvironment,
required AsyncMarker asyncModifier,
required DartType returnType,
required Uri fileUri,
required int fileOffset,
required int length,
}) {
// For async, async*, and sync* functions with declared return types, we
// need to determine whether those types are valid.
// We use the same trick in each case below. For example to decide whether
// Future<T> <: [returnType] for every T, we rely on Future<Bot> and
// transitivity of the subtyping relation because Future<Bot> <: Future<T>
// for every T.
// We use [problem == null] to signal success.
Message? problem;
switch (asyncModifier) {
case AsyncMarker.Async:
DartType futureBottomType = libraryBuilder.loader.futureOfBottom;
if (!typeEnvironment.isSubtypeOf(futureBottomType, returnType)) {
problem = codeIllegalAsyncReturnType;
}
break;
case AsyncMarker.AsyncStar:
DartType streamBottomType = libraryBuilder.loader.streamOfBottom;
if (returnType is VoidType) {
problem = codeIllegalAsyncGeneratorVoidReturnType;
} else if (!typeEnvironment.isSubtypeOf(streamBottomType, returnType)) {
problem = codeIllegalAsyncGeneratorReturnType;
}
break;
case AsyncMarker.SyncStar:
DartType iterableBottomType = libraryBuilder.loader.iterableOfBottom;
if (returnType is VoidType) {
problem = codeIllegalSyncGeneratorVoidReturnType;
} else if (!typeEnvironment.isSubtypeOf(
iterableBottomType,
returnType,
)) {
problem = codeIllegalSyncGeneratorReturnType;
}
break;
case AsyncMarker.Sync:
break; // skip
}
if (problem != null) {
// TODO(hillerstrom): once types get annotated with location
// information, we can improve the quality of the error message by
// using the offset of [returnType] (and the length of its name).
addProblem(problem, fileOffset, length, fileUri);
}
}
void checkBoundsInConstructorInvocation({
required LibraryFeatures libraryFeatures,
required Constructor constructor,
required List<DartType> typeArguments,
required TypeEnvironment typeEnvironment,
required Uri fileUri,
required int fileOffset,
bool inferred = false,
}) {
if (typeArguments.isEmpty) return;
Class klass = constructor.enclosingClass;
DartType constructedType = new InterfaceType(
klass,
klass.enclosingLibrary.nonNullable,
typeArguments,
);
checkBoundsInType(
libraryFeatures: libraryFeatures,
type: constructedType,
typeEnvironment: typeEnvironment,
fileUri: fileUri,
fileOffset: fileOffset,
inferred: inferred,
allowSuperBounded: false,
);
}
void checkBoundsInFactoryInvocation({
required LibraryFeatures libraryFeatures,
required Procedure factory,
required List<DartType> typeArguments,
required TypeEnvironment typeEnvironment,
required Uri fileUri,
required int fileOffset,
bool inferred = false,
}) {
if (typeArguments.isEmpty) return;
assert(factory.isFactory || factory.isExtensionTypeMember);
DartType constructedType = Substitution.fromPairs(
factory.function.typeParameters,
typeArguments,
).substituteType(factory.function.returnType);
checkBoundsInType(
libraryFeatures: libraryFeatures,
type: constructedType,
typeEnvironment: typeEnvironment,
fileUri: fileUri,
fileOffset: fileOffset,
inferred: inferred,
allowSuperBounded: false,
);
}
void checkBoundsInFunctionInvocation({
required ProblemReportingHelper problemReportingHelper,
required LibraryFeatures libraryFeatures,
required TypeEnvironment typeEnvironment,
required FunctionType functionType,
required String? localName,
required ArgumentsImpl arguments,
required Uri fileUri,
required int fileOffset,
}) {
if (arguments.types.isEmpty) return;
if (functionType.typeParameters.length != arguments.types.length) {
assert(
problemReportingHelper.assertProblemReportedElsewhere(
"SourceLibraryBuilder.checkBoundsInFunctionInvocation: "
"the numbers of type parameters and type arguments don't match.",
expectedPhase: CompilationPhaseForProblemReporting.outline,
),
);
return;
}
final DartType bottomType = const NeverType.nonNullable();
List<TypeArgumentIssue> issues = findTypeArgumentIssuesForInvocation(
getFreshTypeParametersFromStructuralParameters(
functionType.typeParameters,
).freshTypeParameters,
arguments.types,
typeEnvironment,
bottomType,
areGenericArgumentsAllowed: libraryFeatures.genericMetadata.isEnabled,
);
_reportTypeArgumentIssues(
issues,
fileUri,
fileOffset,
inferred: !arguments.hasExplicitTypeArguments,
// TODO(johnniwinther): Special-case messaging on function type
// invocation to avoid reference to 'call' and use the function type
// instead.
targetName: localName ?? 'call',
);
}
void checkBoundsInInstantiation({
required ProblemReportingHelper problemReportingHelper,
required LibraryFeatures libraryFeatures,
required TypeEnvironment typeEnvironment,
required FunctionType functionType,
required List<DartType> typeArguments,
required Uri fileUri,
required int fileOffset,
required bool inferred,
}) {
if (typeArguments.isEmpty) return;
if (functionType.typeParameters.length != typeArguments.length) {
// Coverage-ignore-block(suite): Not run.
assert(
problemReportingHelper.assertProblemReportedElsewhere(
"SourceLibraryBuilder.checkBoundsInInstantiation: "
"the numbers of type parameters and type arguments don't match.",
expectedPhase: CompilationPhaseForProblemReporting.outline,
),
);
return;
}
final DartType bottomType = const NeverType.nonNullable();
List<TypeArgumentIssue> issues = findTypeArgumentIssuesForInvocation(
getFreshTypeParametersFromStructuralParameters(
functionType.typeParameters,
).freshTypeParameters,
typeArguments,
typeEnvironment,
bottomType,
areGenericArgumentsAllowed: libraryFeatures.genericMetadata.isEnabled,
);
_reportTypeArgumentIssues(
issues,
fileUri,
fileOffset,
targetReceiver: functionType,
inferred: inferred,
);
}
void checkBoundsInMethodInvocation({
required ProblemReportingHelper problemReportingHelper,
required LibraryFeatures libraryFeatures,
required DartType receiverType,
required TypeEnvironment typeEnvironment,
required ClassHierarchyBase classHierarchy,
required ClassHierarchyMembers membersHierarchy,
required Name name,
required Member? interfaceTarget,
required ArgumentsImpl arguments,
required Uri fileUri,
required int fileOffset,
}) {
if (arguments.types.isEmpty) return;
Class klass;
List<DartType> receiverTypeArguments;
Map<TypeParameter, DartType> substitutionMap = <TypeParameter, DartType>{};
if (receiverType is InterfaceType) {
klass = receiverType.classNode;
receiverTypeArguments = receiverType.typeArguments;
for (int i = 0; i < receiverTypeArguments.length; ++i) {
substitutionMap[klass.typeParameters[i]] = receiverTypeArguments[i];
}
} else {
return;
}
// TODO(cstefantsova): Find a better way than relying on [interfaceTarget].
Member? method =
membersHierarchy.getDispatchTarget(klass, name) ?? interfaceTarget;
if (method == null || method is! Procedure) {
return;
}
if (klass != method.enclosingClass) {
Supertype parent = classHierarchy.getClassAsInstanceOf(
klass,
method.enclosingClass!,
)!;
klass = method.enclosingClass!;
receiverTypeArguments = parent.typeArguments;
Map<TypeParameter, DartType> instanceSubstitutionMap = substitutionMap;
substitutionMap = <TypeParameter, DartType>{};
for (int i = 0; i < receiverTypeArguments.length; ++i) {
substitutionMap[klass.typeParameters[i]] = substitute(
receiverTypeArguments[i],
instanceSubstitutionMap,
);
}
}
List<TypeParameter> methodParameters = method.function.typeParameters;
if (methodParameters.length != arguments.types.length) {
assert(
problemReportingHelper.assertProblemReportedElsewhere(
"SourceLibraryBuilder.checkBoundsInMethodInvocation: "
"the numbers of type parameters and type arguments don't match.",
expectedPhase: CompilationPhaseForProblemReporting.outline,
),
);
return;
}
List<TypeParameter> methodTypeParametersOfInstantiated =
getFreshTypeParameters(methodParameters).freshTypeParameters;
for (TypeParameter typeParameter in methodTypeParametersOfInstantiated) {
typeParameter.bound = substitute(typeParameter.bound, substitutionMap);
typeParameter.defaultType = substitute(
typeParameter.defaultType,
substitutionMap,
);
}
final DartType bottomType = const NeverType.nonNullable();
List<TypeArgumentIssue> issues = findTypeArgumentIssuesForInvocation(
methodTypeParametersOfInstantiated,
arguments.types,
typeEnvironment,
bottomType,
areGenericArgumentsAllowed: libraryFeatures.genericMetadata.isEnabled,
);
_reportTypeArgumentIssues(
issues,
fileUri,
fileOffset,
inferred: !arguments.hasExplicitTypeArguments,
targetReceiver: receiverType,
targetName: name.text,
);
}
void checkBoundsInStaticInvocation({
required ProblemReportingHelper problemReportingHelper,
required LibraryFeatures libraryFeatures,
required String targetName,
required TypeEnvironment typeEnvironment,
required Uri fileUri,
required List<TypeParameter> typeParameters,
required List<DartType> typeArguments,
required bool explicitTypeArguments,
required int fileOffset,
}) {
if (typeArguments.isEmpty) return;
if (typeParameters.length != typeArguments.length) {
assert(
problemReportingHelper.assertProblemReportedElsewhere(
"SourceLibraryBuilder.checkBoundsInStaticInvocation: "
"the numbers of type parameters and type arguments don't match.",
expectedPhase: CompilationPhaseForProblemReporting.outline,
),
);
return;
}
final DartType bottomType = const NeverType.nonNullable();
List<TypeArgumentIssue> issues = findTypeArgumentIssuesForInvocation(
typeParameters,
typeArguments,
typeEnvironment,
bottomType,
areGenericArgumentsAllowed: libraryFeatures.genericMetadata.isEnabled,
);
if (issues.isNotEmpty) {
_reportTypeArgumentIssues(
issues,
fileUri,
fileOffset,
inferred: !explicitTypeArguments,
targetName: targetName,
);
}
}
void checkBoundsInType({
required LibraryFeatures libraryFeatures,
required DartType type,
required TypeEnvironment typeEnvironment,
required Uri fileUri,
required int fileOffset,
bool? inferred,
bool allowSuperBounded = true,
}) {
List<TypeArgumentIssue> issues = findTypeArgumentIssues(
type,
typeEnvironment,
allowSuperBounded: allowSuperBounded,
areGenericArgumentsAllowed: libraryFeatures.genericMetadata.isEnabled,
);
_reportTypeArgumentIssues(issues, fileUri, fileOffset, inferred: inferred);
}
/// Reports an error if [type] contains is a generic function type used as
/// a type argument through its alias.
///
/// For instance
///
/// typedef A = B<void Function<T>(T)>;
///
/// here `A` doesn't use a generic function as type argument directly, but
/// its unaliased value `B<void Function<T>(T)>` does.
///
/// This is used for reporting generic function types used as a type argument,
/// which was disallowed before the 'generic-metadata' feature was enabled.
void checkGenericFunctionTypeAsTypeArgumentThroughTypedef({
required LibraryFeatures libraryFeatures,
required TypedefType type,
required Uri fileUri,
required int fileOffset,
}) {
assert(!libraryFeatures.genericMetadata.isEnabled);
if (!hasGenericFunctionTypeAsTypeArgument(type)) {
DartType unaliased = type.unalias;
if (hasGenericFunctionTypeAsTypeArgument(unaliased)) {
addProblem(
codeGenericFunctionTypeAsTypeArgumentThroughTypedef.withArgumentsOld(
unaliased,
type,
),
fileOffset,
noLength,
fileUri,
);
}
}
}
void checkGetterSetterTypes({
required LibraryFeatures libraryFeatures,
required TypeEnvironment typeEnvironment,
required DartType getterType,
required String getterName,
required UriOffsetLength getterUriOffset,
required DartType setterType,
required String setterName,
required UriOffsetLength setterUriOffset,
}) {
if (libraryFeatures.getterSetterError.isEnabled ||
getterType is InvalidType ||
setterType is InvalidType) {
// Don't report a problem because the it isn't considered a problem in the
// current Dart version or because something else is wrong that has
// already been reported.
} else {
bool isValid = typeEnvironment.isSubtypeOf(getterType, setterType);
if (!isValid) {
addProblem2(
codeInvalidGetterSetterType.withArgumentsOld(
getterType,
getterName,
setterType,
setterName,
),
getterUriOffset,
context: [
codeInvalidGetterSetterTypeSetterContext
.withArgumentsOld(setterName)
.withLocation2(setterUriOffset),
],
);
}
}
}
/// Checks that non-nullable optional parameters have a default value.
void checkInitializersInFormals({
required List<FormalParameterBuilder>? formals,
required TypeEnvironment typeEnvironment,
required bool isAbstract,
required bool isExternal,
}) {
if (formals != null && !(isAbstract || isExternal)) {
for (int i = 0; i < formals.length; i++) {
FormalParameterBuilder formal = formals[i];
bool isOptionalPositional =
formal.isOptionalPositional && formal.isPositional;
bool isOptionalNamed = !formal.isRequiredNamed && formal.isNamed;
bool isOptional = isOptionalPositional || isOptionalNamed;
if (isOptional &&
formal.variable!.type.isPotentiallyNonNullable &&
!formal.hasDeclaredInitializer) {
addProblem(
codeOptionalNonNullableWithoutInitializerError.withArgumentsOld(
formal.name,
formal.variable!.type,
),
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
formal.variable?.isErroneouslyInitialized = true;
}
}
}
}
Expression? checkStaticArguments({
required CompilerContext compilerContext,
required Member target,
required Arguments arguments,
required int fileOffset,
required Uri fileUri,
}) {
List<TypeParameter> typeParameters = target.function!.typeParameters;
if (target is Constructor) {
assert(!target.enclosingClass.isAbstract);
typeParameters = target.enclosingClass.typeParameters;
}
LocatedMessage? argMessage = checkArgumentsForFunction(
function: target.function!,
arguments: arguments,
fileOffset: fileOffset,
fileUri: fileUri,
typeParameters: typeParameters,
);
if (argMessage != null) {
return buildProblemWithContextFromMember(
compilerContext: compilerContext,
name: target.name.text,
member: target,
message: argMessage,
fileUri: fileUri,
);
}
return null;
}
void checkTypesInField({
required TypeEnvironment typeEnvironment,
required bool isInstanceMember,
required bool isLate,
required bool isExternal,
required bool hasInitializer,
required DartType fieldType,
required String name,
required int nameLength,
required int nameOffset,
required Uri fileUri,
}) {
// Check that the field has an initializer if its type is potentially
// non-nullable.
// Only static and top-level fields are checked here. Instance fields are
// checked elsewhere.
if (!isInstanceMember &&
!isLate &&
!isExternal &&
fieldType is! InvalidType &&
fieldType.isPotentiallyNonNullable &&
!hasInitializer) {
addProblem(
codeFieldNonNullableWithoutInitializerError.withArgumentsOld(
name,
fieldType,
),
nameOffset,
nameLength,
fileUri,
);
}
}
void reportTypeArgumentIssue({
required Message message,
required Uri fileUri,
required int fileOffset,
TypeParameter? typeParameter,
DartType? superBoundedAttempt,
DartType? superBoundedAttemptInverted,
}) {
List<LocatedMessage>? context;
// Skip reporting location for function-type type parameters as it's a
// limitation of Kernel.
if (typeParameter != null &&
typeParameter.fileOffset != -1 &&
typeParameter.location?.file != null) {
// It looks like when parameters come from augmentation libraries, they
// don't have a reportable location.
(context ??= <LocatedMessage>[]).add(
codeIncorrectTypeArgumentVariable.withLocation(
typeParameter.location!.file,
typeParameter.fileOffset,
noLength,
),
);
}
if (superBoundedAttemptInverted != null && superBoundedAttempt != null) {
// Coverage-ignore-block(suite): Not run.
(context ??= <LocatedMessage>[]).add(
codeSuperBoundedHint
.withArgumentsOld(superBoundedAttempt, superBoundedAttemptInverted)
.withLocation(fileUri, fileOffset, noLength),
);
}
addProblem(message, fileOffset, noLength, fileUri, context: context);
}
Expression wrapInLocatedProblem({
required CompilerContext compilerContext,
required Expression expression,
required LocatedMessage message,
List<LocatedMessage>? context,
bool errorHasBeenReported = false,
bool includeExpression = true,
}) {
// TODO(askesc): Produce explicit error expression wrapping the original.
// See [issue 29717](https://github.com/dart-lang/sdk/issues/29717)
int offset = expression.fileOffset;
if (offset == -1) {
offset = message.charOffset;
}
return buildProblem(
compilerContext: compilerContext,
message: message.messageObject,
fileUri: message.uri!,
fileOffset: message.charOffset,
length: message.length,
context: context,
expression: includeExpression ? expression : null,
errorHasBeenReported: errorHasBeenReported,
);
}
Expression wrapInProblem({
required CompilerContext compilerContext,
required Expression expression,
required Message message,
required Uri fileUri,
required int fileOffset,
required int length,
List<LocatedMessage>? context,
bool? errorHasBeenReported,
bool includeExpression = true,
}) {
CfeSeverity severity = message.code.severity;
if (severity == CfeSeverity.error) {
return wrapInLocatedProblem(
compilerContext: compilerContext,
expression: expression,
message: message.withLocation(fileUri, fileOffset, length),
context: context,
errorHasBeenReported:
errorHasBeenReported ?? expression is InvalidExpression,
includeExpression: includeExpression,
);
} else {
// Coverage-ignore-block(suite): Not run.
if (expression is! InvalidExpression) {
addProblem(message, fileOffset, length, fileUri, context: context);
}
return expression;
}
}
void _reportTypeArgumentIssueForStructuralParameter(
Message message,
Uri fileUri,
int fileOffset, {
TypeParameter? typeParameter,
DartType? superBoundedAttempt,
DartType? superBoundedAttemptInverted,
}) {
List<LocatedMessage>? context;
// Skip reporting location for function-type type parameters as it's a
// limitation of Kernel.
if (typeParameter != null && typeParameter.location != null) {
// It looks like when parameters come from augmentation libraries, they
// don't have a reportable location.
(context ??= <LocatedMessage>[]).add(
codeIncorrectTypeArgumentVariable.withLocation(
typeParameter.location!.file,
typeParameter.fileOffset,
noLength,
),
);
}
if (superBoundedAttemptInverted != null && superBoundedAttempt != null) {
(context ??= // Coverage-ignore(suite): Not run.
<LocatedMessage>[])
.add(
codeSuperBoundedHint
.withArgumentsOld(
superBoundedAttempt,
superBoundedAttemptInverted,
)
.withLocation(fileUri, fileOffset, noLength),
);
}
addProblem(message, fileOffset, noLength, fileUri, context: context);
}
void _reportTypeArgumentIssues(
List<TypeArgumentIssue> issues,
Uri fileUri,
int offset, {
bool? inferred,
DartType? targetReceiver,
String? targetName,
}) {
for (int i = 0; i < issues.length; i++) {
TypeArgumentIssue issue = issues[i];
DartType argument = issue.argument;
TypeParameter? typeParameter = issue.typeParameter;
Message message;
bool issueInferred = inferred ?? false;
if (issue.isGenericTypeAsArgumentIssue) {
if (issueInferred) {
message = codeGenericFunctionTypeInferredAsActualTypeArgument
.withArgumentsOld(argument);
} else {
message = codeGenericFunctionTypeUsedAsActualTypeArgument;
}
typeParameter = null;
} else {
if (issue.enclosingType == null && targetReceiver != null) {
if (targetName != null) {
if (issueInferred) {
message = codeIncorrectTypeArgumentQualifiedInferred
.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
targetReceiver,
targetName,
);
} else {
message = codeIncorrectTypeArgumentQualified.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
targetReceiver,
targetName,
);
}
} else {
if (issueInferred) {
message = codeIncorrectTypeArgumentInstantiationInferred
.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
targetReceiver,
);
} else {
message = codeIncorrectTypeArgumentInstantiation.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
targetReceiver,
);
}
}
} else {
String enclosingName = issue.enclosingType == null
? targetName!
: getGenericTypeName(issue.enclosingType!);
if (issueInferred) {
message = codeIncorrectTypeArgumentInferred.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
enclosingName,
);
} else {
message = codeIncorrectTypeArgument.withArgumentsOld(
argument,
typeParameter.bound,
typeParameter.name!,
enclosingName,
);
}
}
}
// Don't show the hint about an attempted super-bounded type if the issue
// with the argument is that it's generic.
_reportTypeArgumentIssueForStructuralParameter(
message,
fileUri,
offset,
typeParameter: typeParameter,
superBoundedAttempt: issue.isGenericTypeAsArgumentIssue
? null
: issue.enclosingType,
superBoundedAttemptInverted: issue.isGenericTypeAsArgumentIssue
? null
: issue.invertedType,
);
}
}
}