blob: 3ad30a188ba9ad819362e80ef2e1f3edf09a52e6 [file] [log] [blame]
// 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,
InterfaceType,
Library,
NamedType,
NeverType,
Node,
Nullability,
Supertype,
TreeNode,
TypeParameter,
TypeParameterType,
Typedef,
TypedefType,
VoidType,
setParents;
import "package:kernel/src/bounds_checks.dart" show calculateBounds;
import "mock_sdk.dart" show mockSdk;
import "type_parser.dart" as type_parser show parse;
import "type_parser.dart"
show
ParsedClass,
ParsedIntersectionType,
ParsedFunctionType,
ParsedInterfaceType,
ParsedType,
ParsedTypeVariable,
ParsedTypedef,
ParsedVoidType,
Visitor;
import 'type_parser.dart';
Component parseComponent(String source, Uri uri) {
Uri coreUri = Uri.parse("dart:core");
KernelEnvironment coreEnvironment = new KernelEnvironment(coreUri, coreUri);
Library coreLibrary =
parseLibrary(coreUri, mockSdk, environment: coreEnvironment);
KernelEnvironment libraryEnvironment =
new KernelEnvironment(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, KernelEnvironment environment}) {
fileUri ??= uri;
environment ??= new KernelEnvironment(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[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 KernelEnvironment {
final Uri uri;
final Uri fileUri;
final Map<String, TreeNode> declarations = <String, TreeNode>{};
final KernelEnvironment 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>[];
KernelEnvironment(this.uri, this.fileUri, [this.parent]);
Node kernelFromParsedType(ParsedType type) {
Node node = type.accept(const KernelFromParsedType(), this);
return node;
}
bool isObject(String name) => name == "Object" && "$uri" == "dart:core";
Class get objectClass => this["Object"];
TreeNode operator [](String name) {
TreeNode result = declarations[name];
if (result == null && parent != null) {
return parent[name];
}
if (result == null) throw "Not found: $name";
return result;
}
void operator []=(String name, TreeNode declaration) {
TreeNode existing = declarations[name];
if (existing != null) {
throw "Duplicated declaration: $name";
}
declarations[name] = declaration;
}
KernelEnvironment extend(Map<String, TreeNode> declarations) {
return new KernelEnvironment(uri, fileUri, this)
..declarations.addAll(declarations);
}
}
class KernelFromParsedType implements Visitor<Node, KernelEnvironment> {
const KernelFromParsedType();
DartType visitInterfaceType(
ParsedInterfaceType node, KernelEnvironment environment) {
String name = node.name;
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") {
return new NeverType(interpretParsedNullability(node.parsedNullability));
}
TreeNode declaration = environment[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, KernelEnvironment>(this, environment);
}
if (declaration is Class) {
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, kernelArguments,
interpretParsedNullability(node.parsedNullability));
} 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,
null,
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.typeParameterTypeNullability == null) {
environment.pendingNullabilities.add(type);
}
return type;
} else if (declaration is Typedef) {
return new TypedefType(declaration, kernelArguments,
interpretParsedNullability(node.parsedNullability));
} else {
throw "Unhandled ${declaration.runtimeType}";
}
}
Class visitClass(ParsedClass node, KernelEnvironment environment) {
String name = node.name;
Class cls = environment[name];
ParameterEnvironment parameterEnvironment =
computeTypeParameterEnvironment(node.typeVariables, environment);
List<TypeParameter> parameters = parameterEnvironment.parameters;
setParents(parameters, cls);
cls.typeParameters
..clear()
..addAll(parameters);
{
KernelEnvironment environment = parameterEnvironment.environment;
InterfaceType type =
node.supertype?.accept<Node, KernelEnvironment>(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, KernelEnvironment>(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, KernelEnvironment>(this, environment)));
}
}
return cls;
}
Typedef visitTypedef(ParsedTypedef node, KernelEnvironment environment) {
String name = node.name;
Typedef def = environment[name] =
new Typedef(name, null, fileUri: environment.fileUri);
ParameterEnvironment parameterEnvironment =
computeTypeParameterEnvironment(node.typeVariables, environment);
def.typeParameters.addAll(parameterEnvironment.parameters);
DartType type;
{
KernelEnvironment environment = parameterEnvironment.environment;
type = node.type.accept<Node, KernelEnvironment>(this, environment);
if (type is FunctionType) {
FunctionType f = type;
type = new FunctionType(f.positionalParameters, f.returnType,
namedParameters: f.namedParameters,
typeParameters: f.typeParameters,
requiredParameterCount: f.requiredParameterCount,
typedefType: def.thisType,
nullability: Nullability.nonNullable);
}
}
return def..type = type;
}
FunctionType visitFunctionType(
ParsedFunctionType node, KernelEnvironment environment) {
ParameterEnvironment parameterEnvironment =
computeTypeParameterEnvironment(node.typeVariables, environment);
List<DartType> positionalParameters = <DartType>[];
List<NamedType> namedParameters = <NamedType>[];
DartType returnType;
{
KernelEnvironment environment = parameterEnvironment.environment;
returnType =
node.returnType?.accept<Node, KernelEnvironment>(this, environment);
for (ParsedType argument in node.arguments.required) {
positionalParameters
.add(argument.accept<Node, KernelEnvironment>(this, environment));
}
List<Object> optional = node.arguments.optional;
for (int i = 0; i < optional.length; i++) {
ParsedType parsedType = optional[i];
DartType type =
parsedType.accept<Node, KernelEnvironment>(this, environment);
if (node.arguments.optionalAreNamed) {
namedParameters.add(new NamedType(optional[++i], type));
} else {
positionalParameters.add(type);
}
}
}
namedParameters.sort();
return new FunctionType(positionalParameters, returnType,
namedParameters: namedParameters,
requiredParameterCount: node.arguments.required.length,
typeParameters: parameterEnvironment.parameters,
nullability: interpretParsedNullability(node.parsedNullability));
}
VoidType visitVoidType(ParsedVoidType node, KernelEnvironment environment) {
return const VoidType();
}
TypeParameter visitTypeVariable(
ParsedTypeVariable node, KernelEnvironment environment) {
throw "not implemented: $node";
}
TypeParameterType visitIntersectionType(
ParsedIntersectionType node, KernelEnvironment environment) {
TypeParameterType type =
node.a.accept<Node, KernelEnvironment>(this, environment);
DartType bound = node.b.accept<Node, KernelEnvironment>(this, environment);
return new TypeParameterType(type.parameter, bound, type.nullability);
}
Supertype toSupertype(InterfaceType type) {
return new Supertype.byReference(type.className, type.typeArguments);
}
ParameterEnvironment computeTypeParameterEnvironment(
List<ParsedTypeVariable> typeVariables, KernelEnvironment 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);
}
KernelEnvironment 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, const <DartType>[], Nullability.nullable)
..defaultType = const DynamicType();
} else {
DartType type =
bound.accept<Node, KernelEnvironment>(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);
for (int i = 0; i < typeParameters.length; i++) {
typeParameters[i].defaultType = defaultTypes[i];
}
for (TypeParameterType type in nestedEnvironment.pendingNullabilities) {
type.typeParameterTypeNullability =
TypeParameterType.computeNullabilityFromBound(type.parameter);
}
nestedEnvironment.pendingNullabilities.clear();
return new ParameterEnvironment(typeParameters, nestedEnvironment);
}
}
class ParameterEnvironment {
final List<TypeParameter> parameters;
final KernelEnvironment environment;
const ParameterEnvironment(this.parameters, this.environment);
}