| // 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" |
| show |
| BottomType, |
| Class, |
| Component, |
| DartType, |
| DynamicType, |
| FunctionType, |
| FutureOrType, |
| InterfaceType, |
| Library, |
| NamedType, |
| NeverType, |
| Node, |
| Nullability, |
| Supertype, |
| TreeNode, |
| TypeParameter, |
| TypeParameterType, |
| Typedef, |
| TypedefType, |
| VoidType, |
| setParents; |
| |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| |
| import 'package:kernel/src/bounds_checks.dart' show calculateBounds; |
| |
| import 'package:kernel/testing/mock_sdk.dart' show mockSdk; |
| |
| import 'package:kernel/testing/type_parser.dart' as type_parser show parse; |
| |
| import 'package:kernel/testing/type_parser.dart' |
| show |
| ParsedClass, |
| ParsedIntersectionType, |
| ParsedFunctionType, |
| ParsedInterfaceType, |
| ParsedType, |
| ParsedTypeVariable, |
| ParsedTypedef, |
| ParsedVoidType, |
| Visitor; |
| import 'package:kernel/testing/type_parser.dart'; |
| |
| Component parseComponent(String source, Uri uri) { |
| Uri coreUri = Uri.parse("dart:core"); |
| TypeParserEnvironment coreEnvironment = |
| new TypeParserEnvironment(coreUri, coreUri); |
| Library coreLibrary = |
| parseLibrary(coreUri, mockSdk, environment: coreEnvironment); |
| TypeParserEnvironment libraryEnvironment = new TypeParserEnvironment(uri, uri) |
| ._extend(coreEnvironment._declarations); |
| Library library = parseLibrary(uri, source, environment: libraryEnvironment); |
| library.name = "lib"; |
| return new Component(libraries: <Library>[coreLibrary, library]); |
| } |
| |
| Library parseLibrary(Uri uri, String text, |
| {Uri fileUri, TypeParserEnvironment environment}) { |
| fileUri ??= uri; |
| environment ??= new TypeParserEnvironment(uri, fileUri); |
| Library library = |
| new Library(uri, fileUri: fileUri, name: uri.path.replaceAll("/", ".")); |
| List<ParsedType> types = type_parser.parse(text); |
| for (ParsedType type in types) { |
| if (type is ParsedClass) { |
| String name = type.name; |
| environment._registerDeclaration( |
| name, |
| new Class(fileUri: fileUri, name: name) |
| ..typeParameters.addAll(new List<TypeParameter>.filled( |
| type.typeVariables.length, null))); |
| } |
| } |
| for (ParsedType type in types) { |
| Node node = environment._kernelFromParsedType(type); |
| if (node is Class) { |
| library.addClass(node); |
| } else if (node is Typedef) { |
| library.addTypedef(node); |
| } else { |
| throw "Unsupported: $node"; |
| } |
| } |
| return library; |
| } |
| |
| class Env { |
| Component component; |
| |
| CoreTypes coreTypes; |
| |
| TypeParserEnvironment _libraryEnvironment; |
| |
| Env(String source) { |
| Uri libraryUri = Uri.parse('memory:main.dart'); |
| Uri coreUri = Uri.parse("dart:core"); |
| TypeParserEnvironment coreEnvironment = |
| new TypeParserEnvironment(coreUri, coreUri); |
| Library coreLibrary = |
| parseLibrary(coreUri, mockSdk, environment: coreEnvironment); |
| _libraryEnvironment = new TypeParserEnvironment(libraryUri, libraryUri) |
| ._extend(coreEnvironment._declarations); |
| Library library = |
| parseLibrary(libraryUri, source, environment: _libraryEnvironment); |
| library.name = "lib"; |
| component = new Component(libraries: <Library>[coreLibrary, library]); |
| coreTypes = new CoreTypes(component); |
| } |
| |
| DartType parseType(String text) { |
| return _libraryEnvironment.parseType(text); |
| } |
| |
| void extendWithTypeParameters(String typeParameters) { |
| _libraryEnvironment = |
| _libraryEnvironment.extendWithTypeParameters(typeParameters); |
| } |
| |
| void withTypeParameters(String typeParameters, void Function() f) { |
| TypeParserEnvironment oldLibraryEnvironment = _libraryEnvironment; |
| extendWithTypeParameters(typeParameters); |
| f(); |
| _libraryEnvironment = oldLibraryEnvironment; |
| } |
| } |
| |
| class TypeParserEnvironment { |
| final Uri uri; |
| |
| final Uri fileUri; |
| |
| final Map<String, TreeNode> _declarations = <String, TreeNode>{}; |
| |
| final TypeParserEnvironment _parent; |
| |
| /// Collects types to set their nullabilities after type parameters are ready. |
| /// |
| /// [TypeParameterType]s may receive their nullability at the declaration or |
| /// from the bound of the [TypeParameter]s they refer to. If a |
| /// [TypeParameterType] is allocated at the time when the bound of the |
| /// [TypeParameter] is not set yet, that is, if it's encountered in that |
| /// bound or the bound of other [TypeParameter] from the same scope, and the |
| /// nullability of that [TypeParameterType] is not set at declaration, the |
| /// [TypeParameterType] is added to [pendingNullabilities], so that it can be |
| /// updated when the bound of the [TypeParameter] is ready. |
| final List<TypeParameterType> pendingNullabilities = <TypeParameterType>[]; |
| |
| TypeParserEnvironment(this.uri, this.fileUri, [this._parent]); |
| |
| Node _kernelFromParsedType(ParsedType type) { |
| Node node = type.accept(const _KernelFromParsedType(), this); |
| return node; |
| } |
| |
| DartType parseType(String text) { |
| return _kernelFromParsedType(type_parser.parse(text).single); |
| } |
| |
| bool isObject(String name) => name == "Object" && "$uri" == "dart:core"; |
| |
| Class get objectClass => lookupDeclaration("Object"); |
| |
| TreeNode lookupDeclaration(String name) { |
| TreeNode result = _declarations[name]; |
| if (result == null && _parent != null) { |
| return _parent.lookupDeclaration(name); |
| } |
| if (result == null) throw "Not found: $name"; |
| return result; |
| } |
| |
| TreeNode _registerDeclaration(String name, TreeNode declaration) { |
| TreeNode existing = _declarations[name]; |
| if (existing != null) { |
| throw "Duplicated declaration: $name"; |
| } |
| return _declarations[name] = declaration; |
| } |
| |
| TypeParserEnvironment _extend(Map<String, TreeNode> declarations) { |
| return new TypeParserEnvironment(uri, fileUri, this) |
| .._declarations.addAll(declarations); |
| } |
| |
| TypeParserEnvironment extendWithTypeParameters(String typeParameters) { |
| if (typeParameters?.isEmpty ?? true) return this; |
| return const _KernelFromParsedType() |
| .computeTypeParameterEnvironment( |
| parseTypeVariables("<${typeParameters}>"), this) |
| .environment; |
| } |
| |
| /// Returns the predefined type by the [name], if any. |
| /// |
| /// Use this in subclasses to add support for additional predefined types. |
| DartType getPredefinedNamedType(String name) { |
| if (_parent != null) { |
| return _parent.getPredefinedNamedType(name); |
| } |
| return null; |
| } |
| } |
| |
| class _KernelFromParsedType implements Visitor<Node, TypeParserEnvironment> { |
| const _KernelFromParsedType(); |
| |
| DartType visitInterfaceType( |
| ParsedInterfaceType node, TypeParserEnvironment environment) { |
| String name = node.name; |
| DartType predefined = environment.getPredefinedNamedType(name); |
| if (predefined != null) { |
| return predefined; |
| } else if (name == "dynamic") { |
| // Don't return a const object to ensure we test implementations that use |
| // identical. |
| return new DynamicType(); |
| } else if (name == "void") { |
| // Don't return a const object to ensure we test implementations that use |
| // identical. |
| return new VoidType(); |
| } else if (name == "bottom") { |
| // Don't return a const object to ensure we test implementations that use |
| // identical. |
| return new BottomType(); |
| } else if (name == "Never") { |
| // Don't return a const object to ensure we test implementations that use |
| // identical. |
| return new NeverType(interpretParsedNullability(node.parsedNullability)); |
| } |
| TreeNode declaration = environment.lookupDeclaration(name); |
| List<ParsedType> arguments = node.arguments; |
| List<DartType> kernelArguments = |
| new List<DartType>.filled(arguments.length, null); |
| for (int i = 0; i < arguments.length; i++) { |
| kernelArguments[i] = |
| arguments[i].accept<Node, TypeParserEnvironment>(this, environment); |
| } |
| if (name == "FutureOr") { |
| return new FutureOrType(kernelArguments.single, |
| interpretParsedNullability(node.parsedNullability)); |
| } |
| if (declaration is Class) { |
| Nullability nullability = |
| interpretParsedNullability(node.parsedNullability); |
| if (declaration.name == 'Null' && |
| declaration.enclosingLibrary.importUri.scheme == 'dart' && |
| declaration.enclosingLibrary.importUri.path == 'core') { |
| if (node.parsedNullability != ParsedNullability.omitted) { |
| throw "Null type must be written without explicit nullability"; |
| } |
| nullability = Nullability.nullable; |
| } |
| List<TypeParameter> typeVariables = declaration.typeParameters; |
| if (kernelArguments.isEmpty && typeVariables.isNotEmpty) { |
| kernelArguments = new List<DartType>.filled(typeVariables.length, null); |
| for (int i = 0; i < typeVariables.length; i++) { |
| kernelArguments[i] = typeVariables[i].defaultType; |
| } |
| } else if (kernelArguments.length != typeVariables.length) { |
| throw "Expected ${typeVariables.length} type arguments: $node"; |
| } |
| return new InterfaceType(declaration, nullability, kernelArguments); |
| } else if (declaration is TypeParameter) { |
| if (arguments.isNotEmpty) { |
| throw "Type variable can't have arguments (${node.name})"; |
| } |
| Nullability nullability = declaration.bound == null |
| ? null |
| : TypeParameterType.computeNullabilityFromBound(declaration); |
| TypeParameterType type = new TypeParameterType( |
| declaration, |
| interpretParsedNullability(node.parsedNullability, |
| ifOmitted: nullability)); |
| // If the nullability was omitted on the type and can't be computed from |
| // the bound because it's not yet available, it will be set to null. In |
| // that case, put it to the list to be updated later, when the bound is |
| // available. |
| if (type.declaredNullability == null) { |
| environment.pendingNullabilities.add(type); |
| } |
| return type; |
| } else if (declaration is Typedef) { |
| return new TypedefType(declaration, |
| interpretParsedNullability(node.parsedNullability), kernelArguments); |
| } else { |
| throw "Unhandled ${declaration.runtimeType}"; |
| } |
| } |
| |
| Class visitClass(ParsedClass node, TypeParserEnvironment environment) { |
| String name = node.name; |
| Class cls = environment.lookupDeclaration(name); |
| ParameterEnvironment parameterEnvironment = |
| computeTypeParameterEnvironment(node.typeVariables, environment); |
| List<TypeParameter> parameters = parameterEnvironment.parameters; |
| setParents(parameters, cls); |
| cls.typeParameters |
| ..clear() |
| ..addAll(parameters); |
| { |
| TypeParserEnvironment environment = parameterEnvironment.environment; |
| InterfaceType type = node.supertype |
| ?.accept<Node, TypeParserEnvironment>(this, environment); |
| if (type == null) { |
| if (!environment.isObject(name)) { |
| cls.supertype = environment.objectClass.asRawSupertype; |
| } |
| } else { |
| cls.supertype = toSupertype(type); |
| } |
| InterfaceType mixedInType = node.mixedInType |
| ?.accept<Node, TypeParserEnvironment>(this, environment); |
| if (mixedInType != null) { |
| cls.mixedInType = toSupertype(mixedInType); |
| } |
| List<ParsedType> interfaces = node.interfaces; |
| for (int i = 0; i < interfaces.length; i++) { |
| cls.implementedTypes.add(toSupertype(interfaces[i] |
| .accept<Node, TypeParserEnvironment>(this, environment))); |
| } |
| } |
| return cls; |
| } |
| |
| Typedef visitTypedef(ParsedTypedef node, TypeParserEnvironment environment) { |
| String name = node.name; |
| Typedef def = environment._registerDeclaration( |
| name, new Typedef(name, null, fileUri: environment.fileUri)); |
| ParameterEnvironment parameterEnvironment = |
| computeTypeParameterEnvironment(node.typeVariables, environment); |
| def.typeParameters.addAll(parameterEnvironment.parameters); |
| DartType type; |
| { |
| TypeParserEnvironment environment = parameterEnvironment.environment; |
| type = node.type.accept<Node, TypeParserEnvironment>(this, environment); |
| if (type is FunctionType) { |
| FunctionType f = type; |
| type = new FunctionType( |
| f.positionalParameters, f.returnType, Nullability.nonNullable, |
| namedParameters: f.namedParameters, |
| typeParameters: f.typeParameters, |
| requiredParameterCount: f.requiredParameterCount, |
| typedefType: new TypedefType( |
| def, |
| Nullability.nonNullable, |
| def.typeParameters |
| .map((p) => new TypeParameterType( |
| p, TypeParameterType.computeNullabilityFromBound(p))) |
| .toList())); |
| } |
| } |
| return def..type = type; |
| } |
| |
| FunctionType visitFunctionType( |
| ParsedFunctionType node, TypeParserEnvironment environment) { |
| ParameterEnvironment parameterEnvironment = |
| computeTypeParameterEnvironment(node.typeVariables, environment); |
| List<DartType> positionalParameters = <DartType>[]; |
| List<NamedType> namedParameters = <NamedType>[]; |
| DartType returnType; |
| { |
| TypeParserEnvironment environment = parameterEnvironment.environment; |
| returnType = node.returnType |
| ?.accept<Node, TypeParserEnvironment>(this, environment); |
| for (ParsedType argument in node.arguments.required) { |
| positionalParameters.add( |
| argument.accept<Node, TypeParserEnvironment>(this, environment)); |
| } |
| for (ParsedType argument in node.arguments.positional) { |
| positionalParameters.add( |
| argument.accept<Node, TypeParserEnvironment>(this, environment)); |
| } |
| for (ParsedNamedArgument argument in node.arguments.named) { |
| namedParameters.add(new NamedType( |
| argument.name, |
| argument.type |
| .accept<Node, TypeParserEnvironment>(this, environment), |
| isRequired: argument.isRequired)); |
| } |
| } |
| namedParameters.sort(); |
| return new FunctionType(positionalParameters, returnType, |
| interpretParsedNullability(node.parsedNullability), |
| namedParameters: namedParameters, |
| requiredParameterCount: node.arguments.required.length, |
| typeParameters: parameterEnvironment.parameters); |
| } |
| |
| VoidType visitVoidType( |
| ParsedVoidType node, TypeParserEnvironment environment) { |
| return const VoidType(); |
| } |
| |
| TypeParameter visitTypeVariable( |
| ParsedTypeVariable node, TypeParserEnvironment environment) { |
| throw "not implemented: $node"; |
| } |
| |
| TypeParameterType visitIntersectionType( |
| ParsedIntersectionType node, TypeParserEnvironment environment) { |
| TypeParameterType type = |
| node.a.accept<Node, TypeParserEnvironment>(this, environment); |
| DartType bound = |
| node.b.accept<Node, TypeParserEnvironment>(this, environment); |
| return new TypeParameterType.intersection( |
| type.parameter, type.nullability, bound); |
| } |
| |
| Supertype toSupertype(InterfaceType type) { |
| return new Supertype.byReference(type.className, type.typeArguments); |
| } |
| |
| ParameterEnvironment computeTypeParameterEnvironment( |
| List<ParsedTypeVariable> typeVariables, |
| TypeParserEnvironment environment) { |
| List<TypeParameter> typeParameters = |
| new List<TypeParameter>.filled(typeVariables.length, null); |
| Map<String, TypeParameter> typeParametersByName = <String, TypeParameter>{}; |
| for (int i = 0; i < typeVariables.length; i++) { |
| String name = typeVariables[i].name; |
| typeParametersByName[name] = typeParameters[i] = new TypeParameter(name); |
| } |
| TypeParserEnvironment nestedEnvironment = |
| environment._extend(typeParametersByName); |
| Class objectClass = environment.objectClass; |
| for (int i = 0; i < typeVariables.length; i++) { |
| ParsedType bound = typeVariables[i].bound; |
| TypeParameter typeParameter = typeParameters[i]; |
| if (bound == null) { |
| typeParameter |
| ..bound = new InterfaceType( |
| objectClass, Nullability.nullable, const <DartType>[]) |
| ..defaultType = const DynamicType(); |
| } else { |
| DartType type = |
| bound.accept<Node, TypeParserEnvironment>(this, nestedEnvironment); |
| typeParameter |
| ..bound = type |
| // The default type will be overridden below, but we need to set it |
| // so [calculateBounds] can distinguish between explicit and implicit |
| // bounds. |
| ..defaultType = type; |
| } |
| } |
| List<DartType> defaultTypes = calculateBounds(typeParameters, objectClass, |
| new Library(new Uri.file("test.lib"))..isNonNullableByDefault = true); |
| for (int i = 0; i < typeParameters.length; i++) { |
| typeParameters[i].defaultType = defaultTypes[i]; |
| } |
| |
| for (TypeParameterType type in nestedEnvironment.pendingNullabilities) { |
| type.declaredNullability = |
| TypeParameterType.computeNullabilityFromBound(type.parameter); |
| } |
| nestedEnvironment.pendingNullabilities.clear(); |
| return new ParameterEnvironment(typeParameters, nestedEnvironment); |
| } |
| } |
| |
| class ParameterEnvironment { |
| final List<TypeParameter> parameters; |
| final TypeParserEnvironment environment; |
| |
| const ParameterEnvironment(this.parameters, this.environment); |
| } |