blob: 1419fae2aa74d5bc952cd9dc231b28286d7dd212 [file]
// Copyright (c) 2019, 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/analysis/features.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.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/listener.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
/// Verifier for [CollectionElement]s in list, set, or map literals.
class LiteralElementVerifier {
final TypeProvider typeProvider;
final TypeSystemImpl typeSystem;
final DiagnosticReporter _diagnosticReporter;
final FeatureSet featureSet;
final ErrorVerifier _errorVerifier;
final bool forList;
final bool forSet;
final TypeImpl? elementType;
final bool forMap;
final TypeImpl? mapKeyType;
final TypeImpl? mapValueType;
LiteralElementVerifier(
this.typeProvider,
this.typeSystem,
this._diagnosticReporter,
this._errorVerifier, {
this.forList = false,
this.forSet = false,
this.elementType,
this.forMap = false,
this.mapKeyType,
this.mapValueType,
required this.featureSet,
});
bool get _strictCasts => _errorVerifier.options.strictCasts;
void verify(CollectionElement element) {
_verifyElement(element as CollectionElementImpl);
}
/// Check that the given [type] is assignable to the [elementType], otherwise
/// report the list or set error on the [errorNode].
void _checkAssignableToElementType(TypeImpl type, AstNode errorNode) {
var elementType = this.elementType;
if (!typeSystem.isAssignableTo(
type,
elementType!,
strictCasts: _strictCasts,
)) {
bool assignableWhenNullable = typeSystem.isAssignableTo(
type,
typeSystem.makeNullable(elementType),
strictCasts: _strictCasts,
);
var errorCode = switch ((
forList: forList,
assignableWhenNullable: assignableWhenNullable,
)) {
(forList: false, assignableWhenNullable: false) =>
diag.setElementTypeNotAssignable,
(forList: false, assignableWhenNullable: true) =>
diag.setElementTypeNotAssignableNullability,
(forList: true, assignableWhenNullable: false) =>
diag.listElementTypeNotAssignable,
(forList: true, assignableWhenNullable: true) =>
diag.listElementTypeNotAssignableNullability,
};
_diagnosticReporter.report(
errorCode
.withArguments(actualType: type, expectedType: elementType)
.at(errorNode),
);
}
}
/// Verify that the given [element] can be assigned to the [elementType] of
/// the enclosing list, set, of map literal.
void _verifyElement(CollectionElementImpl? element) {
switch (element) {
case ExpressionImpl():
if (forList || forSet) {
if (elementType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(element)) {
return;
}
_checkAssignableToElementType(element.typeOrThrow, element);
} else {
_diagnosticReporter.report(diag.expressionInMap.at(element));
}
case ForElementImpl():
_verifyElement(element.body2);
case IfElementImpl():
_verifyElement(element.thenElement2);
_verifyElement(element.elseElement2);
case MapLiteralEntryImpl():
if (forMap) {
_verifyMapLiteralEntry(element);
} else {
_diagnosticReporter.report(diag.mapEntryNotInMap.at(element));
}
case SpreadElementImpl():
var isNullAware = element.isNullAware;
Expression expression = element.expression2;
if (forList || forSet) {
_verifySpreadForListOrSet(isNullAware, expression);
} else if (forMap) {
_verifySpreadForMap(isNullAware, expression);
}
case NullAwareElementImpl():
if (forList || forSet) {
var valueType = element.value2.typeOrThrow;
// A null-aware marker tests this expression for `null`, so a `void`
// value is used even when the stored element type is also `void`.
if (valueType is VoidType) {
_errorVerifier.checkForUseOfVoidResult(element.value2);
return;
}
_checkAssignableToElementType(
typeSystem.promoteToNonNull(valueType),
element,
);
} else {
_diagnosticReporter.report(diag.expressionInMap.at(element));
}
case null:
break;
}
}
/// Verify that the [entry]'s key and value are assignable to [mapKeyType]
/// and [mapValueType].
void _verifyMapLiteralEntry(MapLiteralEntry entry) {
var mapKeyType = this.mapKeyType!;
var keyType = entry.key2.typeOrThrow;
// A null-aware marker tests this expression for `null`, so a `void` value
// is used even when the stored key type is also `void`.
if (entry.keyQuestion != null && keyType is VoidType) {
_errorVerifier.checkForUseOfVoidResult(entry.key2);
return;
}
if (mapKeyType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(entry.key2)) {
return;
}
var mapValueType = this.mapValueType!;
var valueType = entry.value2.typeOrThrow;
// A null-aware marker tests this expression for `null`, so a `void` value
// is used even when the stored value type is also `void`.
if (entry.valueQuestion != null && valueType is VoidType) {
_errorVerifier.checkForUseOfVoidResult(entry.value2);
return;
}
if (mapValueType is! VoidType &&
_errorVerifier.checkForUseOfVoidResult(entry.value2)) {
return;
}
// If the key is null-aware, the entry is only added when the key is not
// `null`, so the key type to check should be promoted to non-null.
if (entry.keyQuestion != null) {
keyType = typeSystem.promoteToNonNull(keyType);
}
if (!typeSystem.isAssignableTo(
keyType,
mapKeyType,
strictCasts: _strictCasts,
)) {
if (entry.keyQuestion == null &&
typeSystem.isAssignableTo(
keyType,
typeSystem.makeNullable(mapKeyType),
strictCasts: _strictCasts,
)) {
_diagnosticReporter.report(
diag.mapKeyTypeNotAssignableNullability
.withArguments(actualType: keyType, expectedType: mapKeyType)
.at(entry.key2),
);
} else {
_diagnosticReporter.report(
diag.mapKeyTypeNotAssignable
.withArguments(actualType: keyType, expectedType: mapKeyType)
.at(entry.key2),
);
}
}
// If the value is null-aware, the entry is only added when the value is not
// `null`, so the value type to check should be promoted to non-null.
if (entry.valueQuestion != null) {
valueType = typeSystem.promoteToNonNull(valueType);
}
if (!typeSystem.isAssignableTo(
valueType,
mapValueType,
strictCasts: _strictCasts,
)) {
if (entry.valueQuestion == null &&
typeSystem.isAssignableTo(
valueType,
typeSystem.makeNullable(mapValueType),
strictCasts: _strictCasts,
)) {
_diagnosticReporter.report(
diag.mapValueTypeNotAssignableNullability
.withArguments(actualType: valueType, expectedType: mapValueType)
.at(entry.value2),
);
} else {
_diagnosticReporter.report(
diag.mapValueTypeNotAssignable
.withArguments(actualType: valueType, expectedType: mapValueType)
.at(entry.value2),
);
}
}
}
/// Verify that the type of the elements of the given [expression] can be
/// assigned to the [elementType] of the enclosing collection.
void _verifySpreadForListOrSet(bool isNullAware, Expression expression) {
var expressionType = expression.typeOrThrow;
if (expressionType is DynamicType) {
if (_errorVerifier.strictCasts) {
_diagnosticReporter.report(diag.notIterableSpread.at(expression));
}
return;
}
if (typeSystem.isSubtypeOf(expressionType, NeverTypeImpl.instance)) {
return;
}
if (typeSystem.isSubtypeOf(expressionType, typeSystem.nullNone)) {
if (isNullAware) {
return;
}
_diagnosticReporter.report(diag.notNullAwareNullSpread.at(expression));
return;
}
var iterableType = expressionType.asInstanceOf(
typeProvider.iterableElement,
);
if (iterableType == null) {
_diagnosticReporter.report(diag.notIterableSpread.at(expression));
return;
}
var iterableElementType = iterableType.typeArguments[0];
var elementType = this.elementType;
if (!typeSystem.isAssignableTo(
iterableElementType,
elementType!,
strictCasts: _strictCasts,
)) {
var errorCode = forList
? diag.listElementTypeNotAssignable
: diag.setElementTypeNotAssignable;
// Also check for an "implicit tear-off conversion" which would be applied
// after desugaring a spread element.
var implicitCallMethod = _errorVerifier.getImplicitCallMethod(
iterableElementType,
elementType,
expression,
);
if (implicitCallMethod == null) {
_diagnosticReporter.report(
errorCode
.withArguments(
actualType: iterableElementType,
expectedType: elementType,
)
.at(expression),
);
} else {
var tearoffType = implicitCallMethod.type;
if (featureSet.isEnabled(Feature.constructor_tearoffs)) {
var typeArguments = typeSystem.inferFunctionTypeInstantiation(
elementType as FunctionTypeImpl,
tearoffType,
diagnosticReporter: _diagnosticReporter,
errorNode: expression,
genericMetadataIsEnabled: true,
inferenceUsingBoundsIsEnabled: featureSet.isEnabled(
Feature.inference_using_bounds,
),
strictInference: _errorVerifier.options.strictInference,
strictCasts: _errorVerifier.options.strictCasts,
typeSystemOperations: _errorVerifier.typeSystemOperations,
dataForTesting: null,
nodeForTesting: null,
);
if (typeArguments.isNotEmpty) {
tearoffType = tearoffType.instantiate(typeArguments);
}
}
if (!typeSystem.isAssignableTo(
tearoffType,
elementType,
strictCasts: _strictCasts,
)) {
_diagnosticReporter.report(
errorCode
.withArguments(
actualType: iterableElementType,
expectedType: elementType,
)
.at(expression),
);
}
}
}
}
/// Verify that the [expression] is a subtype of `Map<Object, Object>`, and
/// its key and values are assignable to [mapKeyType] and [mapValueType].
void _verifySpreadForMap(bool isNullAware, Expression expression) {
var expressionType = expression.typeOrThrow;
if (expressionType is DynamicType) {
if (_errorVerifier.strictCasts) {
_diagnosticReporter.report(diag.notMapSpread.at(expression));
}
return;
}
if (typeSystem.isSubtypeOf(expressionType, NeverTypeImpl.instance)) {
return;
}
if (typeSystem.isSubtypeOf(expressionType, typeSystem.nullNone)) {
if (isNullAware) {
return;
}
_diagnosticReporter.report(diag.notNullAwareNullSpread.at(expression));
return;
}
var mapType = expressionType.asInstanceOf(typeProvider.mapElement);
if (mapType == null) {
_diagnosticReporter.report(diag.notMapSpread.at(expression));
return;
}
var keyType = mapType.typeArguments[0];
var mapKeyType = this.mapKeyType;
if (!typeSystem.isAssignableTo(
keyType,
mapKeyType!,
strictCasts: _strictCasts,
)) {
_diagnosticReporter.report(
diag.mapKeyTypeNotAssignable
.withArguments(actualType: keyType, expectedType: mapKeyType)
.at(expression),
);
}
var valueType = mapType.typeArguments[1];
var mapValueType = this.mapValueType;
if (!typeSystem.isAssignableTo(
valueType,
mapValueType!,
strictCasts: _strictCasts,
)) {
_diagnosticReporter.report(
diag.mapValueTypeNotAssignable
.withArguments(actualType: valueType, expectedType: mapValueType)
.at(expression),
);
}
}
}