| // 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:kernel/ast.dart'; | |
| import '../base/lookup_result.dart'; | |
| import '../base/messages.dart'; | |
| import '../builder/builder.dart'; | |
| import '../builder/declaration_builders.dart'; | |
| import '../builder/library_builder.dart'; | |
| import '../builder/member_builder.dart'; | |
| import '../builder/type_builder.dart'; | |
| import '../kernel_generator_impl.dart'; | |
| import '../source/source_loader.dart'; | |
| import '../type_inference/type_schema.dart'; | |
| /// Helper methods to use in annotated tests. | |
| /// Returns a canonical simple name for [member]. | |
| String getMemberName(Member member) { | |
| if (member is Procedure && member.isSetter) return '${member.name.text}='; | |
| return member.name.text; | |
| } | |
| /// Returns a canonical qualified name for [member]. | |
| String getQualifiedMemberName(Member member) { | |
| if (member.enclosingClass != null) { | |
| return '${member.enclosingClass!.name}.${getMemberName(member)}'; | |
| } | |
| return getMemberName(member); | |
| } | |
| /// Returns the enclosing [Member] for [node]. | |
| Member getEnclosingMember(TreeNode? node) { | |
| while (node is! Member) { | |
| node = node!.parent; | |
| } | |
| return node; | |
| } | |
| /// Finds the first [Library] in [component] with the given import [uri]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no library was found. | |
| Library? lookupLibrary(Component component, Uri uri, {bool required = true}) { | |
| for (Library library in component.libraries) { | |
| if (library.importUri == uri) { | |
| return library; | |
| } | |
| for (LibraryPart part in library.parts) { | |
| if (library.fileUri.resolve(part.partUri) == uri) { | |
| return library; | |
| } | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError("Library '$uri' not found."); | |
| } | |
| return null; | |
| } | |
| /// Finds the first [Class] in [library] with the given [className]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no class was found. | |
| Class? lookupClass(Library library, String className, {bool required = true}) { | |
| for (Class cls in library.classes) { | |
| if (cls.name == className) { | |
| return cls; | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError("Class '$className' not found in '$library'."); | |
| } | |
| return null; | |
| } | |
| /// Finds the first [Extension] in [library] with the given [extensionName]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no extension was found. | |
| Extension? lookupExtension( | |
| Library library, | |
| String extensionName, { | |
| bool required = true, | |
| }) { | |
| for (Extension extension in library.extensions) { | |
| if (extension.name == extensionName) { | |
| return extension; | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError( | |
| "Extension '$extensionName' not found in '${library.importUri}'.", | |
| ); | |
| } | |
| return null; | |
| } | |
| /// Finds the first [ExtensionTypeDeclaration] in [library] with the given | |
| /// [extensionTypeName]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no extension type was found. | |
| ExtensionTypeDeclaration? lookupExtensionTypeDeclaration( | |
| Library library, | |
| String extensionTypeName, { | |
| bool required = true, | |
| }) { | |
| for (ExtensionTypeDeclaration extensionTypeDeclaration | |
| in library.extensionTypeDeclarations) { | |
| if (extensionTypeDeclaration.name == extensionTypeName) { | |
| return extensionTypeDeclaration; | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError( | |
| "Extension type '$extensionTypeName' not found in " | |
| "'${library.importUri}'.", | |
| ); | |
| } | |
| return null; | |
| } | |
| /// Finds the first [Member] in [library] with the given canonical simple | |
| /// [memberName] as computed by [getMemberName]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no member was found. | |
| Member? lookupLibraryMember( | |
| Library library, | |
| String memberName, { | |
| bool required = true, | |
| }) { | |
| for (Member member in library.members) { | |
| if (getMemberName(member) == memberName) { | |
| return member; | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError("Member '$memberName' not found in '$library'."); | |
| } | |
| return null; | |
| } | |
| /// Finds the first [Member] in [cls] with the given canonical simple | |
| /// [memberName] as computed by [getMemberName]. | |
| /// | |
| /// If [required] is `true` an error is thrown if no member was found. | |
| Member? lookupClassMember( | |
| Class cls, | |
| String memberName, { | |
| bool required = true, | |
| }) { | |
| for (Member member in cls.members) { | |
| if (getMemberName(member) == memberName) { | |
| return member; | |
| } | |
| } | |
| if (required) { | |
| throw new ArgumentError("Member '$memberName' not found in '$cls'."); | |
| } | |
| return null; | |
| } | |
| LibraryBuilder? lookupLibraryBuilder( | |
| InternalCompilerResult compilerResult, | |
| Library library, { | |
| bool required = true, | |
| }) { | |
| SourceLoader loader = compilerResult.kernelTargetForTesting!.loader; | |
| LibraryBuilder? builder = loader.lookupLoadedLibraryBuilder( | |
| library.importUri, | |
| ); | |
| if (builder == null && required) { | |
| throw new ArgumentError("DeclarationBuilder for $library not found."); | |
| } | |
| return builder; | |
| } | |
| ClassBuilder? lookupClassBuilder( | |
| InternalCompilerResult compilerResult, | |
| Class cls, { | |
| bool required = true, | |
| }) { | |
| LibraryBuilder libraryBuilder = lookupLibraryBuilder( | |
| compilerResult, | |
| cls.enclosingLibrary, | |
| required: required, | |
| )!; | |
| ClassBuilder? clsBuilder = | |
| libraryBuilder.libraryNameSpace.lookup(cls.name)?.getable | |
| as ClassBuilder?; | |
| if (clsBuilder == null && required) { | |
| throw new ArgumentError("ClassBuilder for $cls not found."); | |
| } | |
| return clsBuilder; | |
| } | |
| ExtensionBuilder? lookupExtensionBuilder( | |
| InternalCompilerResult compilerResult, | |
| Extension extension, { | |
| bool required = true, | |
| }) { | |
| LibraryBuilder libraryBuilder = lookupLibraryBuilder( | |
| compilerResult, | |
| extension.enclosingLibrary, | |
| required: required, | |
| )!; | |
| ExtensionBuilder? extensionBuilder; | |
| libraryBuilder.libraryExtensions.forEachLocalExtension(( | |
| ExtensionBuilder builder, | |
| ) { | |
| if (builder.extension == extension) { | |
| extensionBuilder = builder; | |
| } | |
| }); | |
| if (extensionBuilder == null && required) { | |
| throw new ArgumentError("ExtensionBuilder for $extension not found."); | |
| } | |
| return extensionBuilder; | |
| } | |
| ExtensionTypeDeclarationBuilder? lookupExtensionTypeDeclarationBuilder( | |
| InternalCompilerResult compilerResult, | |
| ExtensionTypeDeclaration extensionTypeDeclaration, { | |
| bool required = true, | |
| }) { | |
| LibraryBuilder libraryBuilder = lookupLibraryBuilder( | |
| compilerResult, | |
| extensionTypeDeclaration.enclosingLibrary, | |
| required: required, | |
| )!; | |
| ExtensionTypeDeclarationBuilder? extensionTypeDeclarationBuilder; | |
| Builder? builder = libraryBuilder.libraryNameSpace | |
| .lookup(extensionTypeDeclaration.name) | |
| ?.getable; | |
| if (builder is ExtensionTypeDeclarationBuilder && | |
| builder.extensionTypeDeclaration == extensionTypeDeclaration) { | |
| extensionTypeDeclarationBuilder = builder; | |
| } | |
| if (extensionTypeDeclarationBuilder == null && required) { | |
| throw new ArgumentError( | |
| "ExtensionTypeDeclarationBuilder for $extensionTypeDeclaration " | |
| "not found.", | |
| ); | |
| } | |
| return extensionTypeDeclarationBuilder; | |
| } | |
| /// Look up the [MemberBuilder] for [member] through the [ClassBuilder] for | |
| /// [cls] using [memberName] as its name. | |
| MemberBuilder? lookupClassMemberBuilder( | |
| InternalCompilerResult compilerResult, | |
| Class cls, | |
| Member member, | |
| String memberName, { | |
| bool required = true, | |
| }) { | |
| ClassBuilder? classBuilder = lookupClassBuilder( | |
| compilerResult, | |
| cls, | |
| required: required, | |
| ); | |
| MemberBuilder? memberBuilder; | |
| if (classBuilder != null) { | |
| if (member is Constructor || member is Procedure && member.isFactory) { | |
| memberBuilder = classBuilder.nameSpace | |
| .lookupConstructor(memberName) | |
| ?.getable; | |
| } else { | |
| bool isSetter = member is Procedure && member.isSetter; | |
| LookupResult? result = classBuilder.nameSpace.lookup(memberName); | |
| memberBuilder = | |
| (isSetter ? result?.setable : result?.getable) as MemberBuilder?; | |
| } | |
| } | |
| if (memberBuilder == null && required) { | |
| throw new ArgumentError("MemberBuilder for $member not found."); | |
| } | |
| return memberBuilder; | |
| } | |
| MemberBuilder? lookupMemberBuilder( | |
| InternalCompilerResult compilerResult, | |
| Member member, { | |
| bool required = true, | |
| }) { | |
| MemberBuilder? memberBuilder; | |
| if (member.isExtensionMember) { | |
| String memberName = member.name.text; | |
| String extensionName = memberName.substring(0, memberName.indexOf('|')); | |
| memberName = memberName.substring(extensionName.length + 1); | |
| bool isSetter = member is Procedure && member.isSetter; | |
| if (memberName.startsWith('set#')) { | |
| memberName = memberName.substring(4); | |
| isSetter = true; | |
| } else if (memberName.startsWith('get#')) { | |
| memberName = memberName.substring(4); | |
| } | |
| Extension extension = lookupExtension( | |
| member.enclosingLibrary, | |
| extensionName, | |
| required: true, | |
| )!; | |
| memberBuilder = lookupExtensionMemberBuilder( | |
| compilerResult, | |
| extension, | |
| member, | |
| memberName, | |
| isSetter: isSetter, | |
| required: required, | |
| ); | |
| } else if (member.isExtensionTypeMember) { | |
| String memberName = member.name.text; | |
| String extensionTypeName = memberName.substring(0, memberName.indexOf('|')); | |
| memberName = memberName.substring(extensionTypeName.length + 1); | |
| bool isConstructor = false; | |
| bool isSetter = member is Procedure && member.isSetter; | |
| if (memberName.startsWith('set#')) { | |
| memberName = memberName.substring(4); | |
| isSetter = true; | |
| } else if (memberName.startsWith('get#')) { | |
| memberName = memberName.substring(4); | |
| } else if (memberName.startsWith('constructor#')) { | |
| memberName = memberName.substring(12); | |
| isConstructor = true; | |
| } | |
| ExtensionTypeDeclaration extensionType = lookupExtensionTypeDeclaration( | |
| member.enclosingLibrary, | |
| extensionTypeName, | |
| required: true, | |
| )!; | |
| memberBuilder = lookupExtensionTypeMemberBuilder( | |
| compilerResult, | |
| extensionType, | |
| member, | |
| memberName, | |
| isConstructor: isConstructor, | |
| isSetter: isSetter, | |
| required: required, | |
| ); | |
| } else if (member.enclosingClass != null) { | |
| memberBuilder = lookupClassMemberBuilder( | |
| compilerResult, | |
| member.enclosingClass!, | |
| member, | |
| member.name.text, | |
| required: required, | |
| ); | |
| } else { | |
| LibraryBuilder libraryBuilder = lookupLibraryBuilder( | |
| compilerResult, | |
| member.enclosingLibrary, | |
| required: required, | |
| )!; | |
| bool isSetter = member is Procedure && member.isSetter; | |
| LookupResult? result = libraryBuilder.libraryNameSpace.lookup( | |
| member.name.text, | |
| ); | |
| memberBuilder = | |
| (isSetter ? result?.setable : result?.getable) as MemberBuilder?; | |
| } | |
| if (memberBuilder == null && required) { | |
| throw new ArgumentError("MemberBuilder for $member not found."); | |
| } | |
| return memberBuilder; | |
| } | |
| /// Look up the [MemberBuilder] for [member] through the [ExtensionBuilder] for | |
| /// [extension] using [memberName] as its name. | |
| MemberBuilder? lookupExtensionMemberBuilder( | |
| InternalCompilerResult compilerResult, | |
| Extension extension, | |
| Member member, | |
| String memberName, { | |
| bool isSetter = false, | |
| bool required = true, | |
| }) { | |
| ExtensionBuilder? extensionBuilder = lookupExtensionBuilder( | |
| compilerResult, | |
| extension, | |
| required: required, | |
| ); | |
| MemberBuilder? memberBuilder; | |
| if (extensionBuilder != null) { | |
| bool isSetter = member is Procedure && member.isSetter; | |
| LookupResult? result = extensionBuilder.nameSpace.lookup(memberName); | |
| memberBuilder = | |
| (isSetter ? result?.setable : result?.getable) as MemberBuilder?; | |
| } | |
| if (memberBuilder == null && required) { | |
| throw new ArgumentError("MemberBuilder for $member not found."); | |
| } | |
| return memberBuilder; | |
| } | |
| /// Look up the [MemberBuilder] for [member] through the [ExtensionBuilder] for | |
| /// [extensionType] using [memberName] as its name. | |
| MemberBuilder? lookupExtensionTypeMemberBuilder( | |
| InternalCompilerResult compilerResult, | |
| ExtensionTypeDeclaration extensionType, | |
| Member member, | |
| String memberName, { | |
| bool isConstructor = false, | |
| bool isSetter = false, | |
| bool required = true, | |
| }) { | |
| ExtensionTypeDeclarationBuilder? extensionTypeBuilder = | |
| lookupExtensionTypeDeclarationBuilder( | |
| compilerResult, | |
| extensionType, | |
| required: required, | |
| ); | |
| MemberBuilder? memberBuilder; | |
| if (extensionTypeBuilder != null) { | |
| if (isConstructor) { | |
| memberBuilder = extensionTypeBuilder.nameSpace | |
| .lookupConstructor(memberName) | |
| ?.getable; | |
| } else { | |
| bool isSetter = member is Procedure && member.isSetter; | |
| LookupResult? result = extensionTypeBuilder.nameSpace.lookup(memberName); | |
| memberBuilder = | |
| (isSetter ? result?.setable : result?.getable) as MemberBuilder?; | |
| } | |
| } | |
| if (memberBuilder == null && required) { | |
| throw new ArgumentError("MemberBuilder for $member not found."); | |
| } | |
| return memberBuilder; | |
| } | |
| /// Returns a textual representation of the constant [node] to be used in | |
| /// testing. | |
| String constantToText( | |
| Constant node, { | |
| TypeRepresentation typeRepresentation = TypeRepresentation.legacy, | |
| }) { | |
| StringBuffer sb = new StringBuffer(); | |
| new ConstantToTextVisitor(sb, typeRepresentation).visit(node); | |
| return sb.toString(); | |
| } | |
| enum TypeRepresentation { | |
| legacy, | |
| explicit, | |
| // The type representation is made match the non-nullable-by-default type | |
| // display string from the analyzer. | |
| analyzerNonNullableByDefault, | |
| } | |
| /// Returns a textual representation of the type [node] to be used in | |
| /// testing. | |
| String typeToText( | |
| DartType node, [ | |
| TypeRepresentation typeRepresentation = TypeRepresentation.legacy, | |
| ]) { | |
| StringBuffer sb = new StringBuffer(); | |
| new DartTypeToTextVisitor(sb, typeRepresentation).visit(node); | |
| return sb.toString(); | |
| } | |
| Set<Class> computeAllSuperclasses(Class node) { | |
| Set<Class> set = <Class>{}; | |
| _getAllSuperclasses(node, set); | |
| return set; | |
| } | |
| void _getAllSuperclasses(Class node, Set<Class> set) { | |
| if (set.add(node)) { | |
| if (node.supertype != null) { | |
| _getAllSuperclasses(node.supertype!.classNode, set); | |
| } | |
| if (node.mixedInType != null) { | |
| _getAllSuperclasses(node.mixedInType!.classNode, set); | |
| } | |
| for (Supertype interface in node.implementedTypes) { | |
| _getAllSuperclasses(interface.classNode, set); | |
| } | |
| } | |
| } | |
| String supertypeToText( | |
| Supertype node, [ | |
| TypeRepresentation typeRepresentation = TypeRepresentation.legacy, | |
| ]) { | |
| StringBuffer sb = new StringBuffer(); | |
| sb.write(node.classNode.name); | |
| if (node.typeArguments.isNotEmpty) { | |
| sb.write('<'); | |
| new DartTypeToTextVisitor( | |
| sb, | |
| typeRepresentation, | |
| ).visitList(node.typeArguments); | |
| sb.write('>'); | |
| } | |
| return sb.toString(); | |
| } | |
| class ConstantToTextVisitor implements ConstantVisitor<void> { | |
| final StringBuffer sb; | |
| final DartTypeToTextVisitor typeToText; | |
| new(this.sb, TypeRepresentation typeRepresentation) | |
| : typeToText = new DartTypeToTextVisitor(sb, typeRepresentation); | |
| void visit(Constant node) => node.accept(this); | |
| void visitList(Iterable<Constant> nodes) { | |
| String comma = ''; | |
| for (Constant node in nodes) { | |
| sb.write(comma); | |
| visit(node); | |
| comma = ','; | |
| } | |
| } | |
| @override | |
| void visitNullConstant(NullConstant node) { | |
| sb.write('Null()'); | |
| } | |
| @override | |
| void visitBoolConstant(BoolConstant node) { | |
| sb.write('Bool(${node.value})'); | |
| } | |
| @override | |
| void visitIntConstant(IntConstant node) { | |
| sb.write('Int(${node.value})'); | |
| } | |
| @override | |
| void visitDoubleConstant(DoubleConstant node) { | |
| sb.write('Double(${node.value})'); | |
| } | |
| @override | |
| void visitStringConstant(StringConstant node) { | |
| sb.write('String(${node.value})'); | |
| } | |
| @override | |
| void visitSymbolConstant(SymbolConstant node) { | |
| sb.write('Symbol(${node.name})'); | |
| } | |
| @override | |
| void visitMapConstant(MapConstant node) { | |
| sb.write('Map<'); | |
| typeToText.visit(node.keyType); | |
| sb.write(','); | |
| typeToText.visit(node.valueType); | |
| sb.write('>('); | |
| String comma = ''; | |
| for (ConstantMapEntry entry in node.entries) { | |
| sb.write(comma); | |
| entry.key.accept(this); | |
| sb.write(':'); | |
| entry.value.accept(this); | |
| comma = ','; | |
| } | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitListConstant(ListConstant node) { | |
| sb.write('List<'); | |
| typeToText.visit(node.typeArgument); | |
| sb.write('>('); | |
| visitList(node.entries); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitSetConstant(SetConstant node) { | |
| sb.write('Set<'); | |
| typeToText.visit(node.typeArgument); | |
| sb.write('>('); | |
| visitList(node.entries); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitRecordConstant(RecordConstant node) { | |
| sb.write('Record('); | |
| String comma = ''; | |
| for (Constant field in node.positional) { | |
| sb.write(comma); | |
| field.accept(this); | |
| comma = ','; | |
| } | |
| if (node.named.isNotEmpty) { | |
| sb.write(comma); | |
| sb.write('{'); | |
| comma = ''; | |
| for (MapEntry<String, Constant> entry in node.named.entries) { | |
| sb.write(comma); | |
| sb.write('${entry.key}:'); | |
| entry.value.accept(this); | |
| comma = ','; | |
| } | |
| sb.write('}'); | |
| } | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitInstanceConstant(InstanceConstant node) { | |
| sb.write('Instance('); | |
| sb.write(node.classNode.name); | |
| if (node.typeArguments.isNotEmpty) { | |
| sb.write('<'); | |
| typeToText.visitList(node.typeArguments); | |
| sb.write('>'); | |
| } | |
| if (node.fieldValues.isNotEmpty) { | |
| sb.write(',{'); | |
| String comma = ''; | |
| for (MapEntry<Reference, Constant> entry in node.fieldValues.entries) { | |
| sb.write(comma); | |
| sb.write(getMemberName(entry.key.asField)); | |
| sb.write(':'); | |
| visit(entry.value); | |
| comma = ','; | |
| } | |
| sb.write('}'); | |
| } | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitInstantiationConstant(InstantiationConstant node) { | |
| sb.write('Instantiation('); | |
| Constant tearOffConstant = node.tearOffConstant; | |
| if (tearOffConstant is TearOffConstant) { | |
| sb.write(getMemberName(tearOffConstant.target)); | |
| } else { | |
| visit(tearOffConstant); | |
| } | |
| sb.write('<'); | |
| typeToText.visitList(node.types); | |
| sb.write('>)'); | |
| } | |
| @override | |
| void visitTypedefTearOffConstant(TypedefTearOffConstant node) { | |
| sb.write('TypedefTearOff('); | |
| sb.write(getMemberName(node.tearOffConstant.target)); | |
| if (node.parameters.isNotEmpty) { | |
| sb.write('<'); | |
| for (int i = 0; i < node.parameters.length; i++) { | |
| if (i > 0) { | |
| sb.write(','); | |
| if (typeToText.typeRepresentation == | |
| TypeRepresentation.analyzerNonNullableByDefault) { | |
| sb.write(' '); | |
| } | |
| } | |
| StructuralParameter structuralParameter = node.parameters[i]; | |
| sb.write(structuralParameter.name); | |
| DartType bound = structuralParameter.bound; | |
| if (!(bound is InterfaceType && bound.classNode.name == 'Object')) { | |
| sb.write(' extends '); | |
| typeToText.visit(bound); | |
| } | |
| } | |
| sb.write('>'); | |
| } | |
| sb.write('<'); | |
| typeToText.visitList(node.types); | |
| sb.write('>)'); | |
| } | |
| @override | |
| void visitStaticTearOffConstant(StaticTearOffConstant node) { | |
| sb.write('Function('); | |
| sb.write(getMemberName(node.target)); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitConstructorTearOffConstant(ConstructorTearOffConstant node) { | |
| sb.write('Constructor('); | |
| sb.write(getMemberName(node.target)); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitRedirectingFactoryTearOffConstant( | |
| RedirectingFactoryTearOffConstant node, | |
| ) { | |
| sb.write('RedirectingFactory('); | |
| sb.write(getMemberName(node.target)); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitTypeLiteralConstant(TypeLiteralConstant node) { | |
| sb.write('TypeLiteral('); | |
| typeToText.visit(node.type); | |
| sb.write(')'); | |
| } | |
| @override | |
| void visitUnevaluatedConstant(UnevaluatedConstant node) { | |
| sb.write('Unevaluated()'); | |
| } | |
| @override | |
| bool visitAuxiliaryConstant(AuxiliaryConstant node) { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary constant ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| } | |
| class DartTypeToTextVisitor | |
| with DartTypeVisitorExperimentExclusionMixin<void> | |
| implements DartTypeVisitor<void> { | |
| final StringBuffer sb; | |
| final TypeRepresentation typeRepresentation; | |
| new(this.sb, this.typeRepresentation); | |
| String get commaText { | |
| if (typeRepresentation == TypeRepresentation.analyzerNonNullableByDefault) { | |
| return ', '; | |
| } else { | |
| return ','; | |
| } | |
| } | |
| void visit(DartType node) => node.accept(this); | |
| void visitList(Iterable<DartType> nodes) { | |
| String comma = ''; | |
| for (DartType node in nodes) { | |
| sb.write(comma); | |
| visit(node); | |
| comma = commaText; | |
| } | |
| } | |
| @override | |
| void visitAuxiliaryType(AuxiliaryType node) { | |
| if (node is UnknownType) { | |
| sb.write('_'); | |
| } else { | |
| throw new UnsupportedError( | |
| "Unsupported auxiliary type ${node} (${node.runtimeType}).", | |
| ); | |
| } | |
| } | |
| @override | |
| void visitInvalidType(InvalidType node) { | |
| sb.write('<invalid>'); | |
| } | |
| @override | |
| void visitDynamicType(DynamicType node) { | |
| sb.write('dynamic'); | |
| } | |
| @override | |
| void visitVoidType(VoidType node) { | |
| sb.write('void'); | |
| } | |
| @override | |
| void visitNeverType(NeverType node) { | |
| sb.write('Never'); | |
| if (node.nullability != Nullability.nonNullable) { | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| } | |
| @override | |
| void visitNullType(NullType node) { | |
| sb.write('Null'); | |
| } | |
| @override | |
| void visitInterfaceType(InterfaceType node) { | |
| sb.write(node.classNode.name); | |
| if (node.typeArguments.isNotEmpty) { | |
| sb.write('<'); | |
| visitList(node.typeArguments); | |
| sb.write('>'); | |
| } | |
| if (!isNull(node)) { | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| } | |
| @override | |
| void visitFutureOrType(FutureOrType node) { | |
| sb.write('FutureOr<'); | |
| visit(node.typeArgument); | |
| sb.write('>'); | |
| sb.write(nullabilityToText(node.declaredNullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitFunctionType(FunctionType node) { | |
| visit(node.returnType); | |
| sb.write(' Function'); | |
| if (node.typeParameters.isNotEmpty) { | |
| sb.write('<'); | |
| for (int i = 0; i < node.typeParameters.length; i++) { | |
| if (i > 0) { | |
| sb.write(','); | |
| if (typeRepresentation == | |
| TypeRepresentation.analyzerNonNullableByDefault) { | |
| sb.write(' '); | |
| } | |
| } | |
| StructuralParameter typeParameter = node.typeParameters[i]; | |
| sb.write(typeParameter.name); | |
| DartType bound = typeParameter.bound; | |
| if (!(bound is InterfaceType && bound.classNode.name == 'Object')) { | |
| sb.write(' extends '); | |
| visit(bound); | |
| } | |
| } | |
| sb.write('>'); | |
| } | |
| sb.write('('); | |
| String comma = ''; | |
| visitList(node.positionalParameters.take(node.requiredParameterCount)); | |
| if (node.requiredParameterCount > 0) { | |
| comma = commaText; | |
| } | |
| if (node.requiredParameterCount < node.positionalParameters.length) { | |
| sb.write(comma); | |
| sb.write('['); | |
| visitList(node.positionalParameters.skip(node.requiredParameterCount)); | |
| sb.write(']'); | |
| comma = commaText; | |
| } | |
| if (node.namedParameters.isNotEmpty) { | |
| sb.write(comma); | |
| sb.write('{'); | |
| comma = ''; | |
| for (NamedType namedParameter in node.namedParameters) { | |
| sb.write(comma); | |
| if (namedParameter.isRequired) { | |
| sb.write('required '); | |
| } | |
| visit(namedParameter.type); | |
| sb.write(' '); | |
| sb.write(namedParameter.name); | |
| comma = commaText; | |
| } | |
| sb.write('}'); | |
| } | |
| sb.write(')'); | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitRecordType(RecordType node) { | |
| sb.write('('); | |
| String comma = ''; | |
| visitList(node.positional); | |
| if (node.positional.isNotEmpty) { | |
| comma = commaText; | |
| } | |
| if (node.named.isNotEmpty) { | |
| sb.write(comma); | |
| sb.write('{'); | |
| comma = ''; | |
| for (NamedType namedType in node.named) { | |
| sb.write(comma); | |
| if (namedType.isRequired) { | |
| sb.write('required '); | |
| } | |
| visit(namedType.type); | |
| sb.write(' '); | |
| sb.write(namedType.name); | |
| comma = commaText; | |
| } | |
| sb.write('}'); | |
| } | |
| sb.write(')'); | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitTypeParameterType(TypeParameterType node) { | |
| sb.write(node.parameter.name); | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitStructuralParameterType(StructuralParameterType node) { | |
| sb.write(node.parameter.name); | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitIntersectionType(IntersectionType node) { | |
| visit(node.left); | |
| sb.write(' & '); | |
| visit(node.right); | |
| } | |
| @override | |
| void visitTypedefType(TypedefType node) { | |
| sb.write(node.typedefNode.name); | |
| if (node.typeArguments.isNotEmpty) { | |
| sb.write('<'); | |
| visitList(node.typeArguments); | |
| sb.write('>'); | |
| } | |
| sb.write(nullabilityToText(node.nullability, typeRepresentation)); | |
| } | |
| @override | |
| void visitExtensionType(ExtensionType node) { | |
| sb.write(node.extensionTypeDeclaration.name); | |
| if (node.typeArguments.isNotEmpty) { | |
| sb.write('<'); | |
| visitList(node.typeArguments); | |
| sb.write('>'); | |
| } | |
| sb.write(nullabilityToText(node.declaredNullability, typeRepresentation)); | |
| } | |
| } | |
| /// Returns `true` if [type] is `Object` from `dart:core`. | |
| bool isObject(DartType type) { | |
| return type is InterfaceType && | |
| type.classNode.name == 'Object' && | |
| '${type.classNode.enclosingLibrary.importUri}' == 'dart:core'; | |
| } | |
| /// Returns `true` if [type] is `Null` from `dart:core`. | |
| bool isNull(DartType type) => type is NullType; | |
| /// Returns a textual representation of the [typeParameter] to be used in | |
| /// testing. | |
| String typeParameterToText(TypeParameter typeParameter) { | |
| String name = typeParameter.name!; | |
| if (!isObject(typeParameter.bound)) { | |
| return '$name extends ${typeToText(typeParameter.bound)}'; | |
| } | |
| return name; | |
| } | |
| /// Returns a textual representation of the [type] to be used in testing. | |
| String typeBuilderToText(TypeBuilder type) { | |
| StringBuffer sb = new StringBuffer(); | |
| type.printOn(sb); | |
| return sb.toString(); | |
| } | |
| /// Returns a textual representation of the [typeParameter] to be used in | |
| /// testing. | |
| String typeVariableBuilderToText(NominalParameterBuilder typeParameter) { | |
| String name = typeParameter.name; | |
| if (typeParameter.bound != null) { | |
| return '$name extends ${typeBuilderToText(typeParameter.bound!)}'; | |
| } | |
| return name; | |
| } | |
| /// Returns a textual representation of [errors] to be used in testing. | |
| String errorsToText(List<FormattedMessage> errors, {bool useCodes = false}) { | |
| if (useCodes) { | |
| return errors.map((m) => m.code.name).join(','); | |
| } else { | |
| return errors.map((m) => m.problemMessage).join(','); | |
| } | |
| } | |
| /// Returns a textual representation of [descriptor] to be used in testing. | |
| List<String> extensionMethodDescriptorToText( | |
| ExtensionMemberDescriptor descriptor, | |
| ) { | |
| String descriptorToText(Reference reference, {required bool forTearOff}) { | |
| StringBuffer sb = new StringBuffer(); | |
| if (descriptor.isStatic) { | |
| sb.write('static '); | |
| } | |
| switch (descriptor.kind) { | |
| case ExtensionMemberKind.Method: | |
| if (forTearOff) { | |
| sb.write('tearoff '); | |
| } | |
| break; | |
| case ExtensionMemberKind.Getter: | |
| sb.write('getter '); | |
| break; | |
| case ExtensionMemberKind.Setter: | |
| sb.write('setter '); | |
| break; | |
| case ExtensionMemberKind.Operator: | |
| sb.write('operator '); | |
| break; | |
| case ExtensionMemberKind.Field: | |
| sb.write('field '); | |
| break; | |
| } | |
| sb.write(descriptor.name.text); | |
| sb.write('='); | |
| Member member = reference.asMember; | |
| String name = member.name.text; | |
| if (member is Procedure && member.isSetter) { | |
| sb.write('$name='); | |
| } else { | |
| sb.write(name); | |
| } | |
| return sb.toString(); | |
| } | |
| return [ | |
| if (descriptor.memberReference != null) | |
| descriptorToText(descriptor.memberReference!, forTearOff: false), | |
| if (descriptor.tearOffReference != null) | |
| descriptorToText(descriptor.tearOffReference!, forTearOff: true), | |
| ]; | |
| } | |
| /// Returns a textual representation of [nullability] to be used in testing. | |
| String nullabilityToText( | |
| Nullability nullability, | |
| TypeRepresentation typeRepresentation, | |
| ) { | |
| switch (nullability) { | |
| case Nullability.nonNullable: | |
| switch (typeRepresentation) { | |
| case TypeRepresentation.explicit: | |
| case TypeRepresentation.legacy: | |
| return '!'; | |
| case TypeRepresentation.analyzerNonNullableByDefault: | |
| return ''; | |
| } | |
| case Nullability.nullable: | |
| return '?'; | |
| case Nullability.undetermined: | |
| switch (typeRepresentation) { | |
| case TypeRepresentation.analyzerNonNullableByDefault: | |
| return ''; | |
| default: | |
| return '%'; | |
| } | |
| } | |
| } |