blob: a6ef8283dd0b90e4fe1a664af462aec726c77f04 [file] [log] [blame]
// Copyright (c) 2022, 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:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_types.dart';
import 'package:analyzer/dart/ast/ast.dart';
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/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/replacement_visitor.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/variance.dart';
import 'package:analyzer/src/generated/constant.dart';
Space convertConstantValueToSpace(
AnalyzerExhaustivenessCache cache, DartObjectImpl? constantValue) {
if (constantValue != null) {
InstanceState state = constantValue.state;
if (constantValue.isNull) {
return Space.nullSpace;
} else if (state is BoolState && state.value != null) {
return Space(cache.getBoolValueStaticType(state.value!));
} else if (state is RecordState) {
Map<String, Space> fields = {};
for (int index = 0; index < state.positionalFields.length; index++) {
fields['\$${index + 1}'] =
convertConstantValueToSpace(cache, state.positionalFields[index]);
}
for (MapEntry<String, DartObjectImpl> entry
in state.namedFields.entries) {
fields[entry.key] = convertConstantValueToSpace(cache, entry.value);
}
return Space(cache.getStaticType(constantValue.type), fields);
}
DartType type = constantValue.type;
if (type is InterfaceType && type.element.kind == ElementKind.ENUM) {
return Space(cache.getEnumElementStaticType(
type.element as EnumElement, constantValue));
}
return Space(cache.getUniqueStaticType(
type, constantValue, constantValue.toString()));
}
// TODO(johnniwinther): Assert that constant value is available when the
// exhaustiveness checking is complete.
return Space(cache.getUnknownStaticType());
}
Space convertPatternToSpace(
AnalyzerExhaustivenessCache cache,
DartPattern pattern,
Map<ConstantPattern, DartObjectImpl> constantPatternValues) {
if (pattern is DeclaredVariablePatternImpl) {
DartType type = pattern.declaredElement!.type;
return Space(cache.getStaticType(type));
} else if (pattern is ObjectPattern) {
Map<String, Space> fields = {};
for (PatternField field in pattern.fields) {
PatternFieldName? fieldName = field.name;
String? name;
if (fieldName?.name != null) {
name = fieldName!.name!.lexeme;
} else {
name = field.element?.name;
}
if (name == null) {
// TODO(johnniwinther): How do we handle error cases?
continue;
}
fields[name] =
convertPatternToSpace(cache, field.pattern, constantPatternValues);
}
final type = pattern.type.typeOrThrow;
return Space(cache.getStaticType(type), fields);
} else if (pattern is WildcardPattern) {
final typeNode = pattern.type;
if (typeNode == null) {
return Space.top;
} else {
final type = typeNode.typeOrThrow;
return Space(cache.getStaticType(type));
}
} else if (pattern is RecordPattern) {
int index = 1;
Map<String, Space> fields = {};
List<DartType> positional = [];
Map<String, DartType> named = {};
for (PatternField field in pattern.fields) {
PatternFieldName? fieldName = (field as PatternFieldImpl).name;
String? name;
if (fieldName == null) {
name = '\$${index++}';
positional.add(cache.typeSystem.typeProvider.dynamicType);
} else {
if (fieldName.name != null) {
name = fieldName.name!.lexeme;
} else {
name = field.pattern.variablePattern?.name.lexeme;
}
if (name != null) {
named[name] = cache.typeSystem.typeProvider.dynamicType;
} else {
// Error case, skip field.
continue;
}
}
fields[name] =
convertPatternToSpace(cache, field.pattern, constantPatternValues);
}
RecordType recordType = RecordType(
positional: positional,
named: named,
nullabilitySuffix: NullabilitySuffix.none);
return Space(cache.getStaticType(recordType), fields);
} else if (pattern is LogicalOrPattern) {
return Space.union([
convertPatternToSpace(cache, pattern.leftOperand, constantPatternValues),
convertPatternToSpace(cache, pattern.rightOperand, constantPatternValues)
]);
}
// TODO(johnniwinther): Handle remaining patterns.
DartObjectImpl? value = constantPatternValues[pattern];
return convertConstantValueToSpace(cache, value);
}
class AnalyzerEnumOperations
implements EnumOperations<DartType, EnumElement, FieldElement, DartObject> {
const AnalyzerEnumOperations();
@override
EnumElement? getEnumClass(DartType type) {
Element? element = type.element;
if (element is EnumElement) {
return element;
}
return null;
}
@override
String getEnumElementName(FieldElement enumField) {
return '${enumField.enclosingElement.name}.${enumField.name}';
}
@override
Iterable<FieldElement> getEnumElements(EnumElement enumClass) sync* {
for (FieldElement field in enumClass.fields) {
if (field.isEnumConstant) {
yield field;
}
}
}
@override
InterfaceType getEnumElementType(FieldElement enumField) {
return enumField.type as InterfaceType;
}
@override
DartObject getEnumElementValue(FieldElement enumField) {
return enumField.computeConstantValue()!;
}
}
class AnalyzerExhaustivenessCache extends ExhaustivenessCache<DartType,
ClassElement, EnumElement, FieldElement, DartObject> {
final TypeSystemImpl typeSystem;
AnalyzerExhaustivenessCache(this.typeSystem)
: super(
AnalyzerTypeOperations(typeSystem),
const AnalyzerEnumOperations(),
AnalyzerSealedClassOperations(typeSystem));
}
class AnalyzerSealedClassOperations
implements SealedClassOperations<DartType, ClassElement> {
final TypeSystemImpl _typeSystem;
AnalyzerSealedClassOperations(this._typeSystem);
@override
List<ClassElement> getDirectSubclasses(ClassElement sealedClass) {
List<ClassElement> subclasses = [];
LibraryElement library = sealedClass.library;
outer:
for (Element declaration in library.topLevelElements) {
if (declaration != sealedClass && declaration is ClassElement) {
bool checkType(InterfaceType? type) {
if (type?.element == sealedClass) {
subclasses.add(declaration);
return true;
}
return false;
}
if (checkType(declaration.supertype)) {
continue outer;
}
for (InterfaceType mixin in declaration.mixins) {
if (checkType(mixin)) {
continue outer;
}
}
for (InterfaceType interface in declaration.interfaces) {
if (checkType(interface)) {
continue outer;
}
}
}
}
return subclasses;
}
@override
ClassElement? getSealedClass(DartType type) {
Element? element = type.element;
if (element is ClassElementImpl && element.isSealed) {
return element;
}
return null;
}
@override
DartType? getSubclassAsInstanceOf(
ClassElement subClass, covariant InterfaceType sealedClassType) {
InterfaceType thisType = subClass.thisType;
InterfaceType asSealedClass =
thisType.asInstanceOf(sealedClassType.element)!;
if (thisType.typeArguments.isEmpty) {
return thisType;
}
bool trivialSubstitution = true;
if (thisType.typeArguments.length == asSealedClass.typeArguments.length) {
for (int i = 0; i < thisType.typeArguments.length; i++) {
if (thisType.typeArguments[i] != asSealedClass.typeArguments[i]) {
trivialSubstitution = false;
break;
}
}
if (trivialSubstitution) {
Substitution substitution = Substitution.fromPairs(
subClass.typeParameters, sealedClassType.typeArguments);
for (int i = 0; i < subClass.typeParameters.length; i++) {
DartType? bound = subClass.typeParameters[i].bound;
if (bound != null &&
!_typeSystem.isSubtypeOf(sealedClassType.typeArguments[i],
substitution.substituteType(bound))) {
trivialSubstitution = false;
break;
}
}
}
} else {
trivialSubstitution = false;
}
if (trivialSubstitution) {
return subClass.instantiate(
typeArguments: sealedClassType.typeArguments,
nullabilitySuffix: NullabilitySuffix.none);
} else {
return TypeParameterReplacer.replaceTypeVariables(_typeSystem, thisType);
}
}
}
class AnalyzerTypeOperations implements TypeOperations<DartType> {
final TypeSystemImpl _typeSystem;
final Map<InterfaceType, Map<String, DartType>> _interfaceFieldTypesCaches =
{};
AnalyzerTypeOperations(this._typeSystem);
@override
DartType get boolType => _typeSystem.typeProvider.boolType;
@override
DartType get nullableObjectType => _typeSystem.objectQuestion;
@override
Map<String, DartType> getFieldTypes(DartType type) {
if (type is InterfaceType) {
return _getInterfaceFieldTypes(type);
} else if (type is RecordType) {
Map<String, DartType> fieldTypes = {};
for (int index = 0; index < type.positionalFields.length; index++) {
RecordTypePositionalField field = type.positionalFields[index];
fieldTypes['\$${index + 1}'] = field.type;
}
for (RecordTypeNamedField field in type.namedFields) {
fieldTypes[field.name] = field.type;
}
return fieldTypes;
}
return const {};
}
@override
DartType getNonNullable(DartType type) {
return _typeSystem.promoteToNonNull(type);
}
@override
bool isBoolType(DartType type) {
return type.isDartCoreBool && !isNullable(type);
}
@override
bool isGeneric(DartType type) {
return type is InterfaceType && type.typeArguments.isNotEmpty;
}
@override
bool isNeverType(DartType type) {
return type is NeverType;
}
@override
bool isNonNullableObject(DartType type) {
return type.isDartCoreObject && !isNullable(type);
}
@override
bool isNullable(DartType type) {
return type.nullabilitySuffix == NullabilitySuffix.question;
}
@override
bool isNullableObject(DartType type) {
return type.isDartCoreObject && isNullable(type);
}
@override
bool isNullType(DartType type) {
return type.isDartCoreNull;
}
@override
bool isRecordType(DartType type) {
return type is RecordType && !isNullable(type);
}
@override
bool isSubtypeOf(DartType s, DartType t) {
return _typeSystem.isSubtypeOf(s, t);
}
@override
DartType overapproximate(DartType type) {
return TypeParameterReplacer.replaceTypeVariables(_typeSystem, type);
}
@override
String typeToString(DartType type) => type.toString();
Map<String, DartType> _getInterfaceFieldTypes(InterfaceType type) {
Map<String, DartType>? fieldTypes = _interfaceFieldTypesCaches[type];
if (fieldTypes == null) {
_interfaceFieldTypesCaches[type] = fieldTypes = {};
for (InterfaceType supertype in type.allSupertypes) {
fieldTypes.addAll(_getInterfaceFieldTypes(supertype));
}
for (PropertyAccessorElement accessor in type.accessors) {
if (accessor.isGetter && !accessor.isStatic) {
fieldTypes[accessor.name] = accessor.type.returnType;
}
}
}
return fieldTypes;
}
}
/// Data gathered by the exhaustiveness computation, retained for testing
/// purposes.
class ExhaustivenessDataForTesting {
/// Map from switch statement/expression nodes to the static type of the
/// scrutinee.
Map<AstNode, StaticType> switchScrutineeType = {};
/// Map from switch case nodes to the space for its pattern/expression.
Map<AstNode, Space> caseSpaces = {};
/// Map from switch case nodes to the remaining space before the case or
/// from statement/expression nodes to the remaining space after all cases.
Map<AstNode, Space> remainingSpaces = {};
/// Map from switch statement/expression/case nodes to the error reported
/// on the node.
Map<AstNode, ExhaustivenessError> errors = {};
}
class TypeParameterReplacer extends ReplacementVisitor {
final TypeSystemImpl _typeSystem;
Variance _variance = Variance.covariant;
TypeParameterReplacer(this._typeSystem);
@override
void changeVariance() {
if (_variance == Variance.covariant) {
_variance = Variance.contravariant;
} else if (_variance == Variance.contravariant) {
_variance = Variance.covariant;
}
}
@override
DartType? visitTypeParameterBound(DartType type) {
Variance savedVariance = _variance;
_variance = Variance.invariant;
DartType? result = type.accept(this);
_variance = savedVariance;
return result;
}
@override
DartType? visitTypeParameterType(TypeParameterType node) {
if (_variance == Variance.contravariant) {
return _replaceTypeParameterTypes(_typeSystem.typeProvider.neverType);
} else {
return _replaceTypeParameterTypes(
(node.element as TypeParameterElementImpl).defaultType!);
}
}
DartType _replaceTypeParameterTypes(DartType type) {
return type.accept(this) ?? type;
}
static DartType replaceTypeVariables(
TypeSystemImpl typeSystem, DartType type) {
return TypeParameterReplacer(typeSystem)._replaceTypeParameterTypes(type);
}
}