| // 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/dart_template_buffer.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/key.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart'; |
| import 'package:_fe_analyzer_shared/src/exhaustiveness/shared.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/types.dart'; |
| import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart' |
| show Variance; |
| import 'package:analyzer/dart/analysis/features.dart'; |
| import 'package:analyzer/dart/constant/value.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/constant/value.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.dart'; |
| import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| |
| /// The buffer that accumulates types and elements as is, so that they |
| /// can be written latter into Dart code that considers imports. It also |
| /// accumulates fragments of text, such as syntax `(`, or names of properties. |
| class AnalyzerDartTemplateBuffer |
| implements DartTemplateBuffer<DartObject, FieldElement, TypeImpl> { |
| final List<MissingPatternPart> parts = []; |
| bool isComplete = true; |
| |
| @override |
| void write(String text) { |
| parts.add(MissingPatternTextPart(text)); |
| } |
| |
| @override |
| void writeBoolValue(bool value) { |
| parts.add(MissingPatternTextPart('$value')); |
| } |
| |
| @override |
| void writeCoreType(String name) { |
| parts.add(MissingPatternTextPart(name)); |
| } |
| |
| @override |
| void writeEnumValue(FieldElement value, String name) { |
| var enumElement = value.enclosingElement; |
| if (enumElement is! EnumElement) { |
| isComplete = false; |
| return; |
| } |
| |
| parts.add( |
| MissingPatternEnumValuePart(enumElement2: enumElement, value2: value), |
| ); |
| } |
| |
| @override |
| void writeGeneralConstantValue(DartObject value, String name) { |
| isComplete = false; |
| } |
| |
| @override |
| void writeGeneralType(TypeImpl type, String name) { |
| parts.add(MissingPatternTypePart(type)); |
| } |
| } |
| |
| class AnalyzerEnumOperations |
| implements EnumOperations<TypeImpl, EnumElement, FieldElement, DartObject> { |
| const AnalyzerEnumOperations(); |
| |
| @override |
| EnumElement? getEnumClass(TypeImpl type) { |
| var element = type.element; |
| if (element is EnumElement) { |
| return element; |
| } |
| return null; |
| } |
| |
| @override |
| String getEnumElementName(FieldElement enumField) { |
| return '${enumField.enclosingElement.name3}.${enumField.name3}'; |
| } |
| |
| @override |
| Iterable<FieldElement> getEnumElements(EnumElement enumClass) sync* { |
| for (var field in enumClass.fields) { |
| if (field.isEnumConstant) { |
| yield field; |
| } |
| } |
| } |
| |
| @override |
| InterfaceTypeImpl getEnumElementType(FieldElement enumField) { |
| return enumField.type as InterfaceTypeImpl; |
| } |
| |
| @override |
| DartObject? getEnumElementValue(FieldElement enumField) { |
| return enumField.computeConstantValue(); |
| } |
| } |
| |
| class AnalyzerExhaustivenessCache |
| extends |
| ExhaustivenessCache< |
| TypeImpl, |
| InterfaceElement, |
| EnumElement, |
| FieldElement, |
| DartObject |
| > { |
| final TypeSystemImpl typeSystem; |
| |
| AnalyzerExhaustivenessCache(this.typeSystem, LibraryElement enclosingLibrary) |
| : super( |
| AnalyzerTypeOperations(typeSystem, enclosingLibrary), |
| const AnalyzerEnumOperations(), |
| AnalyzerSealedClassOperations(typeSystem), |
| ); |
| } |
| |
| class AnalyzerSealedClassOperations |
| implements SealedClassOperations<TypeImpl, InterfaceElementImpl> { |
| final TypeSystemImpl _typeSystem; |
| |
| AnalyzerSealedClassOperations(this._typeSystem); |
| |
| @override |
| List<InterfaceElementImpl> getDirectSubclasses( |
| InterfaceElementImpl sealedClass, |
| ) { |
| List<InterfaceElementImpl> subclasses = []; |
| var library = sealedClass.library; |
| outer: |
| for (var declaration in library.children2) { |
| if (declaration is ExtensionTypeElement) { |
| continue; |
| } |
| if (declaration != sealedClass && declaration is InterfaceElementImpl) { |
| bool checkType(InterfaceTypeImpl? type) { |
| if (type?.element == sealedClass) { |
| subclasses.add(declaration); |
| return true; |
| } |
| return false; |
| } |
| |
| if (checkType(declaration.supertype)) { |
| continue outer; |
| } |
| for (var mixin in declaration.mixins) { |
| if (checkType(mixin)) { |
| continue outer; |
| } |
| } |
| for (var interface in declaration.interfaces) { |
| if (checkType(interface)) { |
| continue outer; |
| } |
| } |
| if (declaration is MixinElementImpl) { |
| for (var type in declaration.superclassConstraints) { |
| if (checkType(type)) { |
| continue outer; |
| } |
| } |
| } |
| } |
| } |
| return subclasses; |
| } |
| |
| @override |
| ClassElementImpl? getSealedClass(TypeImpl type) { |
| var element = type.element; |
| if (element is ClassElementImpl && element.isSealed) { |
| return element; |
| } |
| return null; |
| } |
| |
| @override |
| TypeImpl? getSubclassAsInstanceOf( |
| InterfaceElementImpl subClass, |
| covariant InterfaceTypeImpl sealedClassType, |
| ) { |
| var thisType = subClass.thisType; |
| var asSealedClass = thisType.asInstanceOf2(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.fromPairs2( |
| subClass.typeParameters2, |
| sealedClassType.typeArguments, |
| ); |
| for (int i = 0; i < subClass.typeParameters2.length; i++) { |
| var bound = subClass.typeParameters2[i].bound; |
| if (bound != null && |
| !_typeSystem.isSubtypeOf( |
| sealedClassType.typeArguments[i], |
| substitution.substituteType(bound), |
| )) { |
| trivialSubstitution = false; |
| break; |
| } |
| } |
| } |
| } else { |
| trivialSubstitution = false; |
| } |
| if (trivialSubstitution) { |
| return subClass.instantiateImpl( |
| typeArguments: sealedClassType.typeArguments, |
| nullabilitySuffix: NullabilitySuffix.none, |
| ); |
| } else { |
| return TypeParameterReplacer.replaceTypeVariables(_typeSystem, thisType); |
| } |
| } |
| } |
| |
| class AnalyzerTypeOperations implements TypeOperations<TypeImpl> { |
| final TypeSystemImpl _typeSystem; |
| final LibraryElement _enclosingLibrary; |
| |
| final Map<InterfaceTypeImpl, Map<Key, TypeImpl>> _interfaceFieldTypesCaches = |
| {}; |
| |
| AnalyzerTypeOperations(this._typeSystem, this._enclosingLibrary); |
| |
| @override |
| TypeImpl get boolType => _typeSystem.typeProvider.boolType; |
| |
| @override |
| TypeImpl get nonNullableObjectType => _typeSystem.objectNone; |
| |
| @override |
| TypeImpl get nullableObjectType => _typeSystem.objectQuestion; |
| |
| @override |
| TypeImpl getExtensionTypeErasure(TypeImpl type) { |
| return type.extensionTypeErasure; |
| } |
| |
| @override |
| Map<Key, TypeImpl> getFieldTypes(TypeImpl type) { |
| if (type is InterfaceTypeImpl) { |
| return _getInterfaceFieldTypes(type); |
| } else if (type is RecordTypeImpl) { |
| Map<Key, TypeImpl> fieldTypes = {}; |
| fieldTypes.addAll(getFieldTypes(_typeSystem.typeProvider.objectType)); |
| for (int index = 0; index < type.positionalFields.length; index++) { |
| var field = type.positionalFields[index]; |
| fieldTypes[RecordIndexKey(index)] = field.type; |
| } |
| for (var field in type.namedFields) { |
| fieldTypes[RecordNameKey(field.name)] = field.type; |
| } |
| return fieldTypes; |
| } |
| return getFieldTypes(_typeSystem.typeProvider.objectType); |
| } |
| |
| @override |
| TypeImpl? getFutureOrTypeArgument(TypeImpl type) { |
| return type.isDartAsyncFutureOr ? _typeSystem.futureOrBase(type) : null; |
| } |
| |
| @override |
| TypeImpl? getListElementType(TypeImpl type) { |
| var listType = type.asInstanceOf2(_typeSystem.typeProvider.listElement); |
| if (listType != null) { |
| return listType.typeArguments[0]; |
| } |
| return null; |
| } |
| |
| @override |
| TypeImpl? getListType(TypeImpl type) { |
| return type.asInstanceOf2(_typeSystem.typeProvider.listElement); |
| } |
| |
| @override |
| TypeImpl? getMapValueType(TypeImpl type) { |
| var mapType = type.asInstanceOf2(_typeSystem.typeProvider.mapElement); |
| if (mapType != null) { |
| return mapType.typeArguments[1]; |
| } |
| return null; |
| } |
| |
| @override |
| TypeImpl getNonNullable(TypeImpl type) { |
| return _typeSystem.promoteToNonNull(type); |
| } |
| |
| @override |
| TypeImpl? getTypeVariableBound(TypeImpl type) { |
| if (type is TypeParameterTypeImpl) { |
| return type.bound; |
| } |
| return null; |
| } |
| |
| @override |
| bool hasSimpleName(TypeImpl type) { |
| return type is InterfaceTypeImpl || |
| type is DynamicTypeImpl || |
| type is VoidTypeImpl || |
| type is NeverTypeImpl || |
| // TODO(johnniwinther): What about intersection types? |
| type is TypeParameterTypeImpl; |
| } |
| |
| @override |
| TypeImpl instantiateFuture(TypeImpl type) { |
| return _typeSystem.typeProvider.futureType(type); |
| } |
| |
| @override |
| bool isBoolType(TypeImpl type) { |
| return type.isDartCoreBool && !isNullable(type); |
| } |
| |
| @override |
| bool isDynamic(TypeImpl type) { |
| return type is DynamicTypeImpl; |
| } |
| |
| @override |
| bool isGeneric(TypeImpl type) { |
| return type is InterfaceTypeImpl && type.typeArguments.isNotEmpty; |
| } |
| |
| @override |
| bool isNeverType(TypeImpl type) { |
| return type is NeverTypeImpl; |
| } |
| |
| @override |
| bool isNonNullableObject(TypeImpl type) { |
| return type.isDartCoreObject && !isNullable(type); |
| } |
| |
| @override |
| bool isNullable(TypeImpl type) { |
| return type.nullabilitySuffix == NullabilitySuffix.question; |
| } |
| |
| @override |
| bool isNullableObject(TypeImpl type) { |
| return type.isDartCoreObject && isNullable(type); |
| } |
| |
| @override |
| bool isNullType(TypeImpl type) { |
| return type.isDartCoreNull; |
| } |
| |
| @override |
| bool isPotentiallyNullable(TypeImpl type) => |
| _typeSystem.isPotentiallyNullable(type); |
| |
| @override |
| bool isRecordType(TypeImpl type) { |
| return type is RecordTypeImpl && !isNullable(type); |
| } |
| |
| @override |
| bool isSubtypeOf(TypeImpl s, TypeImpl t) { |
| return _typeSystem.isSubtypeOf(s, t); |
| } |
| |
| @override |
| TypeImpl overapproximate(TypeImpl type) { |
| return TypeParameterReplacer.replaceTypeVariables(_typeSystem, type); |
| } |
| |
| @override |
| String typeToString(TypeImpl type) => type.toString(); |
| |
| Map<Key, TypeImpl> _getInterfaceFieldTypes(InterfaceTypeImpl type) { |
| var fieldTypes = _interfaceFieldTypesCaches[type]; |
| if (fieldTypes == null) { |
| _interfaceFieldTypesCaches[type] = fieldTypes = {}; |
| for (var supertype in type.allSupertypes) { |
| fieldTypes.addAll(_getInterfaceFieldTypes(supertype)); |
| } |
| for (var getter in type.getters) { |
| if (getter.isPrivate && getter.library != _enclosingLibrary) { |
| continue; |
| } |
| var name = getter.name3; |
| if (name == null) { |
| continue; |
| } |
| if (!getter.isStatic) { |
| fieldTypes[NameKey(name)] = getter.type.returnType; |
| } |
| } |
| for (var method in type.methods2) { |
| if (method.isPrivate && method.library != _enclosingLibrary) { |
| continue; |
| } |
| var name = method.name3; |
| if (name == null) { |
| continue; |
| } |
| if (!method.isStatic) { |
| fieldTypes[NameKey(name)] = method.type; |
| } |
| } |
| } |
| return fieldTypes; |
| } |
| } |
| |
| /// Data gathered by the exhaustiveness computation, retained for testing |
| /// purposes. |
| class ExhaustivenessDataForTesting { |
| /// Access to interface for looking up `Object` members on non-interface |
| /// types. |
| final ObjectPropertyLookup objectFieldLookup; |
| |
| /// Map from switch statement/expression nodes to the static type of the |
| /// scrutinee. |
| Map<AstNode, StaticType> switchScrutineeType = {}; |
| |
| /// Map from switch statement/expression nodes the spaces for its cases. |
| Map<AstNode, List<Space>> switchCases = {}; |
| |
| /// Map from switch case nodes to the space for its pattern/expression. |
| Map<AstNode, Space> caseSpaces = {}; |
| |
| /// Map from unreachable switch case nodes to information about their |
| /// unreachability. |
| Map<AstNode, CaseUnreachability> caseUnreachabilities = {}; |
| |
| /// Map from switch statement nodes that are erroneous due to being |
| /// non-exhaustive, to information about their non-exhaustiveness. |
| Map<AstNode, NonExhaustiveness> nonExhaustivenesses = {}; |
| |
| ExhaustivenessDataForTesting(this.objectFieldLookup); |
| } |
| |
| class MissingPatternEnumValuePart extends MissingPatternPart { |
| final EnumElement enumElement2; |
| final FieldElement value2; |
| |
| MissingPatternEnumValuePart({ |
| required this.enumElement2, |
| required this.value2, |
| }); |
| |
| @override |
| String toString() => value2.name3!; |
| } |
| |
| abstract class MissingPatternPart {} |
| |
| class MissingPatternTextPart extends MissingPatternPart { |
| final String text; |
| |
| MissingPatternTextPart(this.text); |
| |
| @override |
| String toString() => text; |
| } |
| |
| class MissingPatternTypePart extends MissingPatternPart { |
| final TypeImpl type; |
| |
| MissingPatternTypePart(this.type); |
| |
| @override |
| String toString() { |
| return type.getDisplayString(); |
| } |
| } |
| |
| class PatternConverter with SpaceCreator<DartPattern, TypeImpl> { |
| final Version languageVersion; |
| final FeatureSet featureSet; |
| final AnalyzerExhaustivenessCache cache; |
| final Map<Expression, DartObjectImpl> mapPatternKeyValues; |
| final Map<ConstantPattern, DartObjectImpl> constantPatternValues; |
| |
| /// If we saw an invalid type, we already have a diagnostic reported, |
| /// and there is no need to verify exhaustiveness. |
| bool hasInvalidType = false; |
| |
| PatternConverter({ |
| required this.languageVersion, |
| required this.featureSet, |
| required this.cache, |
| required this.mapPatternKeyValues, |
| required this.constantPatternValues, |
| }); |
| |
| @override |
| ObjectPropertyLookup get objectFieldLookup => cache; |
| |
| @override |
| TypeOperations<TypeImpl> get typeOperations => cache.typeOperations; |
| |
| @override |
| StaticType createListType( |
| TypeImpl type, |
| ListTypeRestriction<TypeImpl> restriction, |
| ) { |
| return cache.getListStaticType(type, restriction); |
| } |
| |
| @override |
| StaticType createMapType( |
| TypeImpl type, |
| MapTypeRestriction<TypeImpl> restriction, |
| ) { |
| return cache.getMapStaticType(type, restriction); |
| } |
| |
| @override |
| StaticType createStaticType(TypeImpl type) { |
| hasInvalidType |= type is InvalidTypeImpl; |
| return cache.getStaticType(type); |
| } |
| |
| @override |
| StaticType createUnknownStaticType() { |
| return cache.getUnknownStaticType(); |
| } |
| |
| @override |
| Space dispatchPattern( |
| Path path, |
| StaticType contextType, |
| DartPattern pattern, { |
| required bool nonNull, |
| }) { |
| if (pattern is DeclaredVariablePatternImpl) { |
| return createVariableSpace( |
| path, |
| contextType, |
| pattern.declaredElement!.type, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is ObjectPattern) { |
| var properties = <String, DartPattern>{}; |
| var extensionPropertyTypes = <String, TypeImpl>{}; |
| for (var field in pattern.fields) { |
| var name = field.effectiveName; |
| if (name == null) { |
| // Error case, skip field. |
| continue; |
| } |
| properties[name] = field.pattern; |
| var element = field.element; |
| TypeImpl? extensionPropertyType; |
| if (element is PropertyAccessorElement2OrMember && |
| (element.enclosingElement is ExtensionElementImpl || |
| element.enclosingElement is ExtensionTypeElementImpl)) { |
| extensionPropertyType = element.returnType; |
| } else if (element is ExecutableElement2OrMember && |
| (element.enclosingElement is ExtensionElementImpl || |
| element.enclosingElement is ExtensionTypeElementImpl)) { |
| extensionPropertyType = element.type; |
| } |
| if (extensionPropertyType != null) { |
| extensionPropertyTypes[name] = extensionPropertyType; |
| } |
| } |
| return createObjectSpace( |
| path, |
| contextType, |
| pattern.type.typeOrThrow, |
| properties, |
| extensionPropertyTypes, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is WildcardPattern) { |
| return createWildcardSpace( |
| path, |
| contextType, |
| pattern.type?.typeOrThrow, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is RecordPatternImpl) { |
| var positionalTypes = <TypeImpl>[]; |
| var positionalPatterns = <DartPattern>[]; |
| var namedTypes = <String, TypeImpl>{}; |
| var namedPatterns = <String, DartPattern>{}; |
| for (var field in pattern.fields) { |
| var nameNode = field.name; |
| if (nameNode == null) { |
| positionalTypes.add(cache.typeSystem.typeProvider.dynamicType); |
| positionalPatterns.add(field.pattern); |
| } else { |
| String? name = field.effectiveName; |
| if (name != null) { |
| namedTypes[name] = cache.typeSystem.typeProvider.dynamicType; |
| namedPatterns[name] = field.pattern; |
| } else { |
| // Error case, skip field. |
| continue; |
| } |
| } |
| } |
| var recordType = RecordTypeImpl.fromApi( |
| positional: positionalTypes, |
| named: namedTypes, |
| nullabilitySuffix: NullabilitySuffix.none, |
| ); |
| return createRecordSpace( |
| path, |
| contextType, |
| recordType, |
| positionalPatterns, |
| namedPatterns, |
| ); |
| } else if (pattern is LogicalOrPattern) { |
| return createLogicalOrSpace( |
| path, |
| contextType, |
| pattern.leftOperand, |
| pattern.rightOperand, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is NullCheckPattern) { |
| return createNullCheckSpace(path, contextType, pattern.pattern); |
| } else if (pattern is ParenthesizedPattern) { |
| return dispatchPattern( |
| path, |
| contextType, |
| pattern.pattern, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is NullAssertPattern) { |
| return createNullAssertSpace(path, contextType, pattern.pattern); |
| } else if (pattern is CastPattern) { |
| return createCastSpace( |
| path, |
| contextType, |
| pattern.type.typeOrThrow, |
| pattern.pattern, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is LogicalAndPattern) { |
| return createLogicalAndSpace( |
| path, |
| contextType, |
| pattern.leftOperand, |
| pattern.rightOperand, |
| nonNull: nonNull, |
| ); |
| } else if (pattern is RelationalPattern) { |
| return createRelationalSpace(path); |
| } else if (pattern is ListPattern) { |
| var type = pattern.requiredType as InterfaceTypeImpl; |
| assert( |
| type.element == cache.typeSystem.typeProvider.listElement && |
| type.typeArguments.length == 1, |
| ); |
| var elementType = type.typeArguments[0]; |
| List<DartPattern> headElements = []; |
| DartPattern? restElement; |
| List<DartPattern> tailElements = []; |
| bool hasRest = false; |
| for (ListPatternElement element in pattern.elements) { |
| if (element is RestPatternElement) { |
| restElement = element.pattern; |
| hasRest = true; |
| } else if (hasRest) { |
| tailElements.add(element as DartPattern); |
| } else { |
| headElements.add(element as DartPattern); |
| } |
| } |
| return createListSpace( |
| path, |
| type: type, |
| elementType: elementType, |
| headElements: headElements, |
| tailElements: tailElements, |
| restElement: restElement, |
| hasRest: hasRest, |
| hasExplicitTypeArgument: pattern.typeArguments != null, |
| ); |
| } else if (pattern is MapPattern) { |
| var type = pattern.requiredType as InterfaceTypeImpl; |
| assert( |
| type.element == cache.typeSystem.typeProvider.mapElement && |
| type.typeArguments.length == 2, |
| ); |
| var keyType = type.typeArguments[0]; |
| var valueType = type.typeArguments[1]; |
| Map<MapKey, DartPattern> entries = {}; |
| for (MapPatternElement entry in pattern.elements) { |
| if (entry is RestPatternElement) { |
| // Rest patterns are illegal in map patterns, so just skip over it. |
| } else { |
| Expression expression = (entry as MapPatternEntry).key; |
| // TODO(johnniwinther): Assert that we have a constant value. |
| DartObjectImpl? constant = mapPatternKeyValues[expression]; |
| if (constant == null) { |
| return createUnknownSpace(path); |
| } |
| MapKey key = MapKey(constant, constant.state.toString()); |
| entries[key] = entry.value; |
| } |
| } |
| |
| return createMapSpace( |
| path, |
| type: cache.typeSystem.typeProvider.mapType(keyType, valueType), |
| keyType: keyType, |
| valueType: valueType, |
| entries: entries, |
| hasExplicitTypeArguments: pattern.typeArguments != null, |
| ); |
| } else if (pattern is ConstantPattern) { |
| var value = constantPatternValues[pattern]; |
| if (value != null) { |
| return _convertConstantValue(value, path); |
| } |
| hasInvalidType = true; |
| return createUnknownSpace(path); |
| } |
| assert(false, "Unexpected pattern $pattern (${pattern.runtimeType})"); |
| return createUnknownSpace(path); |
| } |
| |
| @override |
| bool hasLanguageVersion(int major, int minor) { |
| return languageVersion >= Version(major, minor, 0); |
| } |
| |
| Space _convertConstantValue(DartObjectImpl value, Path path) { |
| var type = value.type; |
| var state = value.state; |
| if (value.isNull) { |
| return Space(path, StaticType.nullType); |
| } else if (state is BoolState) { |
| var value = state.value; |
| if (value != null) { |
| return Space(path, cache.getBoolValueStaticType(state.value!)); |
| } |
| } else if (state is RecordState) { |
| var properties = <Key, Space>{}; |
| for (var index = 0; index < state.positionalFields.length; index++) { |
| var key = RecordIndexKey(index); |
| var value = state.positionalFields[index]; |
| properties[key] = _convertConstantValue(value, path.add(key)); |
| } |
| for (var entry in state.namedFields.entries) { |
| var key = RecordNameKey(entry.key); |
| properties[key] = _convertConstantValue(entry.value, path.add(key)); |
| } |
| return Space(path, cache.getStaticType(type), properties: properties); |
| } |
| if (type is InterfaceTypeImpl) { |
| var element = type.element; |
| if (element is EnumElementImpl) { |
| return Space(path, cache.getEnumElementStaticType(element, value)); |
| } |
| } |
| |
| StaticType staticType; |
| if (value.hasPrimitiveEquality(featureSet)) { |
| staticType = cache.getUniqueStaticType<DartObjectImpl>( |
| type, |
| value, |
| value.state.toString(), |
| ); |
| } else { |
| // If [value] doesn't have primitive equality we cannot tell if it is |
| // equal to itself. |
| staticType = cache.getUnknownStaticType(); |
| } |
| |
| return Space(path, staticType); |
| } |
| } |
| |
| 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 |
| TypeImpl? visitTypeParameterBound(covariant TypeImpl type) { |
| Variance savedVariance = _variance; |
| _variance = Variance.invariant; |
| var result = type.accept(this); |
| _variance = savedVariance; |
| return result; |
| } |
| |
| @override |
| TypeImpl? visitTypeParameterType(covariant TypeParameterTypeImpl node) { |
| if (_variance == Variance.contravariant) { |
| return _replaceTypeParameterTypes(_typeSystem.typeProvider.neverType); |
| } else { |
| var element = node.element; |
| var defaultType = element.defaultType!; |
| return _replaceTypeParameterTypes(defaultType); |
| } |
| } |
| |
| TypeImpl _replaceTypeParameterTypes(TypeImpl type) { |
| return type.accept(this) ?? type; |
| } |
| |
| static TypeImpl replaceTypeVariables( |
| TypeSystemImpl typeSystem, |
| TypeImpl type, |
| ) { |
| return TypeParameterReplacer(typeSystem)._replaceTypeParameterTypes(type); |
| } |
| } |