| // Copyright (c) 2016, 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. |
| library kernel.analyzer.ast_from_analyzer; |
| |
| import 'package:kernel/ast.dart' as ast; |
| import 'package:kernel/frontend/accessors.dart'; |
| import 'package:kernel/frontend/super_initializers.dart'; |
| import 'package:kernel/log.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| import 'package:kernel/transformations/flags.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/kernel/loader.dart'; |
| import 'package:analyzer/analyzer.dart'; |
| import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
| import 'package:analyzer/src/generated/parser.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| |
| /// Provides reference-level access to libraries, classes, and members. |
| /// |
| /// "Reference level" objects are incomplete nodes that have no children but |
| /// can be used for linking until the loader promotes the node to a higher |
| /// loading level. |
| /// |
| /// The [ReferenceScope] is the most restrictive scope in a hierarchy of scopes |
| /// that provide increasing amounts of contextual information. [TypeScope] is |
| /// used when type parameters might be in scope, and [MemberScope] is used when |
| /// building the body of a [ast.Member]. |
| class ReferenceScope { |
| final ReferenceLevelLoader loader; |
| |
| ReferenceScope(this.loader); |
| |
| bool get strongMode => loader.strongMode; |
| |
| ast.Library getLibraryReference(LibraryElement element) { |
| if (element == null) return null; |
| return loader.getLibraryReference(getBaseElement(element)); |
| } |
| |
| ast.Class getRootClassReference() { |
| return loader.getRootClassReference(); |
| } |
| |
| ast.Class getClassReference(ClassElement element) { |
| return loader.getClassReference(getBaseElement(element)); |
| } |
| |
| ast.Member getMemberReference(Element element) { |
| return loader.getMemberReference(getBaseElement(element)); |
| } |
| |
| static Element getBaseElement(Element element) { |
| while (element is Member) { |
| element = (element as Member).baseElement; |
| } |
| return element; |
| } |
| |
| static bool supportsConcreteGet(Element element) { |
| return (element is PropertyAccessorElement && |
| element.isGetter && |
| !element.isAbstract) || |
| element is FieldElement || |
| element is TopLevelVariableElement || |
| element is MethodElement && !element.isAbstract || |
| isTopLevelFunction(element); |
| } |
| |
| static bool supportsInterfaceGet(Element element) { |
| return (element is PropertyAccessorElement && element.isGetter) || |
| element is FieldElement || |
| element is MethodElement; |
| } |
| |
| static bool supportsConcreteSet(Element element) { |
| return (element is PropertyAccessorElement && |
| element.isSetter && |
| !element.isAbstract) || |
| element is FieldElement && !element.isFinal && !element.isConst || |
| element is TopLevelVariableElement && |
| !element.isFinal && |
| !element.isConst; |
| } |
| |
| static bool supportsInterfaceSet(Element element) { |
| return (element is PropertyAccessorElement && element.isSetter) || |
| element is FieldElement && !element.isFinal && !element.isConst; |
| } |
| |
| static bool supportsConcreteIndexGet(Element element) { |
| return element is MethodElement && |
| element.name == '[]' && |
| !element.isAbstract; |
| } |
| |
| static bool supportsInterfaceIndexGet(Element element) { |
| return element is MethodElement && element.name == '[]'; |
| } |
| |
| static bool supportsConcreteIndexSet(Element element) { |
| return element is MethodElement && |
| element.name == '[]=' && |
| !element.isAbstract; |
| } |
| |
| static bool supportsInterfaceIndexSet(Element element) { |
| return element is MethodElement && element.name == '[]='; |
| } |
| |
| static bool supportsConcreteMethodCall(Element element) { |
| // Note that local functions are not valid targets for method calls because |
| // they are not "methods" or even "procedures" in our AST. |
| return element is MethodElement && !element.isAbstract || |
| isTopLevelFunction(element) || |
| element is ConstructorElement && element.isFactory; |
| } |
| |
| static bool supportsInterfaceMethodCall(Element element) { |
| return element is MethodElement; |
| } |
| |
| static bool supportsConstructorCall(Element element) { |
| return element is ConstructorElement && !element.isFactory; |
| } |
| |
| ast.Member _resolveGet( |
| Element element, Element auxiliary, bool predicate(Element element)) { |
| element = desynthesizeGetter(element); |
| if (predicate(element)) return getMemberReference(element); |
| if (element is PropertyAccessorElement && element.isSetter) { |
| // The getter is sometimes stored as the 'corresponding getter' instead |
| // of the 'auxiliary' element. |
| auxiliary ??= element.correspondingGetter; |
| } |
| auxiliary = desynthesizeGetter(auxiliary); |
| if (predicate(auxiliary)) return getMemberReference(auxiliary); |
| return null; |
| } |
| |
| ast.Member resolveConcreteGet(Element element, Element auxiliary) { |
| return _resolveGet(element, auxiliary, supportsConcreteGet); |
| } |
| |
| ast.Member resolveInterfaceGet(Element element, Element auxiliary) { |
| if (!strongMode) return null; |
| return _resolveGet(element, auxiliary, supportsInterfaceGet); |
| } |
| |
| DartType getterTypeOfElement(Element element) { |
| if (element is VariableElement) { |
| return element.type; |
| } else if (element is PropertyAccessorElement && element.isGetter) { |
| return element.returnType; |
| } else { |
| return null; |
| } |
| } |
| |
| /// Returns the interface target of a `call` dispatch to the given member. |
| /// |
| /// For example, if the member is a field of type C, the target will be the |
| /// `call` method of class C, if it has such a method. |
| /// |
| /// If the class C has a getter or field named `call`, this method returns |
| /// `null` - the static type system does support typed calls with indirection. |
| ast.Member resolveInterfaceFunctionCall(Element element) { |
| if (!strongMode || element == null) return null; |
| return resolveInterfaceFunctionCallOnType(getterTypeOfElement(element)); |
| } |
| |
| /// Returns the `call` method of [callee], if it has one, otherwise `null`. |
| ast.Member resolveInterfaceFunctionCallOnType(DartType callee) { |
| return callee is InterfaceType |
| ? resolveInterfaceMethod(callee.getMethod('call')) |
| : null; |
| } |
| |
| ast.Member _resolveSet( |
| Element element, Element auxiliary, bool predicate(Element element)) { |
| element = desynthesizeSetter(element); |
| if (predicate(element)) { |
| return getMemberReference(element); |
| } |
| if (element is PropertyAccessorElement && element.isSetter) { |
| // The setter is sometimes stored as the 'corresponding setter' instead |
| // of the 'auxiliary' element. |
| auxiliary ??= element.correspondingGetter; |
| } |
| auxiliary = desynthesizeSetter(auxiliary); |
| if (predicate(auxiliary)) { |
| return getMemberReference(auxiliary); |
| } |
| return null; |
| } |
| |
| ast.Member resolveConcreteSet(Element element, Element auxiliary) { |
| return _resolveSet(element, auxiliary, supportsConcreteSet); |
| } |
| |
| ast.Member resolveInterfaceSet(Element element, Element auxiliary) { |
| if (!strongMode) return null; |
| return _resolveSet(element, auxiliary, supportsInterfaceSet); |
| } |
| |
| ast.Member resolveConcreteIndexGet(Element element, Element auxiliary) { |
| if (supportsConcreteIndexGet(element)) { |
| return getMemberReference(element); |
| } |
| if (supportsConcreteIndexGet(auxiliary)) { |
| return getMemberReference(auxiliary); |
| } |
| return null; |
| } |
| |
| ast.Member resolveInterfaceIndexGet(Element element, Element auxiliary) { |
| if (!strongMode) return null; |
| if (supportsInterfaceIndexGet(element)) { |
| return getMemberReference(element); |
| } |
| if (supportsInterfaceIndexGet(auxiliary)) { |
| return getMemberReference(auxiliary); |
| } |
| return null; |
| } |
| |
| ast.Member resolveConcreteIndexSet(Element element, Element auxiliary) { |
| if (supportsConcreteIndexSet(element)) { |
| return getMemberReference(element); |
| } |
| if (supportsConcreteIndexSet(auxiliary)) { |
| return getMemberReference(auxiliary); |
| } |
| return null; |
| } |
| |
| ast.Member resolveInterfaceIndexSet(Element element, Element auxiliary) { |
| if (!strongMode) return null; |
| if (supportsInterfaceIndexSet(element)) { |
| return getMemberReference(element); |
| } |
| if (supportsInterfaceIndexSet(auxiliary)) { |
| return getMemberReference(auxiliary); |
| } |
| return null; |
| } |
| |
| ast.Member resolveConcreteMethod(Element element) { |
| if (supportsConcreteMethodCall(element)) { |
| return getMemberReference(element); |
| } |
| return null; |
| } |
| |
| ast.Member resolveInterfaceMethod(Element element) { |
| if (!strongMode) return null; |
| if (supportsInterfaceMethodCall(element)) { |
| return getMemberReference(element); |
| } |
| return null; |
| } |
| |
| ast.Constructor resolveConstructor(Element element) { |
| if (supportsConstructorCall(element)) { |
| return getMemberReference(element); |
| } |
| return null; |
| } |
| |
| ast.Field resolveField(Element element) { |
| if (element is FieldElement && !element.isSynthetic) { |
| return getMemberReference(element); |
| } |
| return null; |
| } |
| |
| /// A static accessor that generates a 'throw NoSuchMethodError' when a |
| /// read or write access could not be resolved. |
| Accessor staticAccess(String name, Element element, [Element auxiliary]) { |
| return new _StaticAccessor( |
| this, |
| name, |
| resolveConcreteGet(element, auxiliary), |
| resolveConcreteSet(element, auxiliary)); |
| } |
| |
| /// An accessor that generates a 'throw NoSuchMethodError' on both read |
| /// and write access. |
| Accessor unresolvedAccess(String name) { |
| return new _StaticAccessor(this, name, null, null); |
| } |
| } |
| |
| class TypeScope extends ReferenceScope { |
| final Map<TypeParameterElement, ast.TypeParameter> localTypeParameters = |
| <TypeParameterElement, ast.TypeParameter>{}; |
| TypeAnnotationBuilder _typeBuilder; |
| |
| TypeScope(ReferenceLevelLoader loader) : super(loader) { |
| _typeBuilder = new TypeAnnotationBuilder(this); |
| } |
| |
| String get location => '?'; |
| |
| bool get allowClassTypeParameters => false; |
| |
| ast.DartType get defaultTypeParameterBound => getRootClassReference().rawType; |
| |
| ast.TypeParameter tryGetTypeParameterReference(TypeParameterElement element) { |
| return localTypeParameters[element] ?? |
| loader.tryGetClassTypeParameter(element); |
| } |
| |
| ast.TypeParameter getTypeParameterReference(TypeParameterElement element) { |
| return localTypeParameters[element] ?? |
| loader.tryGetClassTypeParameter(element) ?? |
| (localTypeParameters[element] = new ast.TypeParameter(element.name)); |
| } |
| |
| ast.TypeParameter makeTypeParameter(TypeParameterElement element, |
| {ast.DartType bound}) { |
| var typeParameter = getTypeParameterReference(element); |
| assert(bound != null); |
| typeParameter.bound = bound; |
| return typeParameter; |
| } |
| |
| ast.DartType buildType(DartType type) { |
| return _typeBuilder.buildFromDartType(type); |
| } |
| |
| ast.Supertype buildSupertype(DartType type) { |
| if (type is InterfaceType) { |
| var classElement = type.element; |
| if (classElement == null) return getRootClassReference().asRawSupertype; |
| var classNode = getClassReference(classElement); |
| if (classNode.typeParameters.isEmpty || |
| classNode.typeParameters.length != type.typeArguments.length) { |
| return classNode.asRawSupertype; |
| } else { |
| return new ast.Supertype(classNode, |
| type.typeArguments.map(buildType).toList(growable: false)); |
| } |
| } |
| return getRootClassReference().asRawSupertype; |
| } |
| |
| ast.DartType buildTypeAnnotation(AstNode node) { |
| return _typeBuilder.build(node); |
| } |
| |
| ast.DartType buildOptionalTypeAnnotation(AstNode node) { |
| return node == null ? null : _typeBuilder.build(node); |
| } |
| |
| ast.DartType getInferredType(Expression node) { |
| if (!strongMode) return const ast.DynamicType(); |
| // TODO: Is this official way to get the strong-mode inferred type? |
| return buildType(node.staticType); |
| } |
| |
| ast.DartType getInferredTypeArgument(Expression node, int index) { |
| var type = getInferredType(node); |
| return type is ast.InterfaceType && index < type.typeArguments.length |
| ? type.typeArguments[index] |
| : const ast.DynamicType(); |
| } |
| |
| ast.DartType getInferredReturnType(Expression node) { |
| var type = getInferredType(node); |
| return type is ast.FunctionType ? type.returnType : const ast.DynamicType(); |
| } |
| |
| List<ast.DartType> getInferredInvocationTypeArguments( |
| InvocationExpression node) { |
| if (!strongMode) return <ast.DartType>[]; |
| ast.DartType inferredFunctionType = buildType(node.staticInvokeType); |
| ast.DartType genericFunctionType = buildType(node.function.staticType); |
| if (genericFunctionType is ast.FunctionType) { |
| if (genericFunctionType.typeParameters.isEmpty) return <ast.DartType>[]; |
| // Attempt to unify the two types to obtain a substitution of the type |
| // variables. If successful, use the substituted types in the order |
| // they occur in the type parameter list. |
| var substitution = unifyTypes(genericFunctionType.withoutTypeParameters, |
| inferredFunctionType, genericFunctionType.typeParameters.toSet()); |
| if (substitution != null) { |
| return genericFunctionType.typeParameters |
| .map((p) => substitution[p] ?? const ast.DynamicType()) |
| .toList(); |
| } |
| return new List<ast.DartType>.filled( |
| genericFunctionType.typeParameters.length, const ast.DynamicType(), |
| growable: true); |
| } else { |
| return <ast.DartType>[]; |
| } |
| } |
| |
| List<ast.DartType> buildOptionalTypeArgumentList(TypeArgumentList node) { |
| if (node == null) return null; |
| return _typeBuilder.buildList(node.arguments); |
| } |
| |
| List<ast.DartType> buildTypeArgumentList(TypeArgumentList node) { |
| return _typeBuilder.buildList(node.arguments); |
| } |
| |
| List<ast.TypeParameter> buildOptionalTypeParameterList(TypeParameterList node, |
| {bool strongModeOnly: false}) { |
| if (node == null) return <ast.TypeParameter>[]; |
| if (strongModeOnly && !strongMode) return <ast.TypeParameter>[]; |
| return node.typeParameters.map(buildTypeParameter).toList(); |
| } |
| |
| ast.TypeParameter buildTypeParameter(TypeParameter node) { |
| return makeTypeParameter(node.element, |
| bound: buildOptionalTypeAnnotation(node.bound) ?? |
| defaultTypeParameterBound); |
| } |
| |
| ConstructorElement findDefaultConstructor(ClassElement class_) { |
| for (var constructor in class_.constructors) { |
| // Note: isDefaultConstructor checks if the constructor is suitable for |
| // being invoked without arguments. It does not imply that it is |
| // synthetic. |
| if (constructor.isDefaultConstructor && !constructor.isFactory) { |
| return constructor; |
| } |
| } |
| return null; |
| } |
| |
| ast.FunctionNode buildFunctionInterface(FunctionTypedElement element) { |
| var positional = <ast.VariableDeclaration>[]; |
| var named = <ast.VariableDeclaration>[]; |
| int requiredParameterCount = 0; |
| // Initialize type parameters in two passes: put them into scope, |
| // and compute the bounds afterwards while they are all in scope. |
| var typeParameters = <ast.TypeParameter>[]; |
| var typeParameterElements = |
| element is ConstructorElement && element.isFactory |
| ? element.enclosingElement.typeParameters |
| : element.typeParameters; |
| if (strongMode || element is ConstructorElement) { |
| for (var parameter in typeParameterElements) { |
| var parameterNode = new ast.TypeParameter(parameter.name); |
| typeParameters.add(parameterNode); |
| localTypeParameters[parameter] = parameterNode; |
| } |
| } |
| for (int i = 0; i < typeParameters.length; ++i) { |
| var parameter = typeParameterElements[i]; |
| var parameterNode = typeParameters[i]; |
| parameterNode.bound = parameter.bound == null |
| ? defaultTypeParameterBound |
| : buildType(parameter.bound); |
| } |
| for (var parameter in element.parameters) { |
| var parameterNode = new ast.VariableDeclaration(parameter.name, |
| type: buildType(parameter.type)); |
| switch (parameter.parameterKind) { |
| case ParameterKind.REQUIRED: |
| positional.add(parameterNode); |
| ++requiredParameterCount; |
| break; |
| |
| case ParameterKind.POSITIONAL: |
| positional.add(parameterNode); |
| break; |
| |
| case ParameterKind.NAMED: |
| named.add(parameterNode); |
| break; |
| } |
| } |
| var returnType = element is ConstructorElement |
| ? const ast.VoidType() |
| : buildType(element.returnType); |
| return new ast.FunctionNode(null, |
| typeParameters: typeParameters, |
| positionalParameters: positional, |
| namedParameters: named, |
| requiredParameterCount: requiredParameterCount, |
| returnType: returnType)..fileOffset = element.nameOffset; |
| } |
| } |
| |
| class ExpressionScope extends TypeScope { |
| ast.Library currentLibrary; |
| final Map<LocalElement, ast.VariableDeclaration> localVariables = |
| <LocalElement, ast.VariableDeclaration>{}; |
| |
| ExpressionBuilder _expressionBuilder; |
| StatementBuilder _statementBuilder; |
| |
| ExpressionScope(ReferenceLevelLoader loader, this.currentLibrary) |
| : super(loader) { |
| assert(currentLibrary != null); |
| _expressionBuilder = new ExpressionBuilder(this); |
| _statementBuilder = new StatementBuilder(this); |
| } |
| |
| bool get allowThis => false; // Overridden by MemberScope. |
| |
| ast.Name buildName(SimpleIdentifier node) { |
| return new ast.Name(node.name, currentLibrary); |
| } |
| |
| ast.Statement buildStatement(Statement node) { |
| return _statementBuilder.build(node); |
| } |
| |
| ast.Statement buildOptionalFunctionBody(FunctionBody body) { |
| if (body == null || |
| body is EmptyFunctionBody || |
| body is NativeFunctionBody) { |
| return null; |
| } |
| return buildMandatoryFunctionBody(body); |
| } |
| |
| ast.Statement buildMandatoryFunctionBody(FunctionBody body) { |
| try { |
| if (body is BlockFunctionBody) { |
| return buildStatement(body.block); |
| } else if (body is ExpressionFunctionBody) { |
| if (bodyHasVoidReturn(body)) { |
| return new ast.ExpressionStatement(buildExpression(body.expression)); |
| } else { |
| return new ast.ReturnStatement(buildExpression(body.expression)) |
| ..fileOffset = body.expression.offset; |
| } |
| } else { |
| return internalError('Missing function body'); |
| } |
| } on _CompilationError catch (e) { |
| return new ast.ExpressionStatement(buildThrowCompileTimeError(e.message)); |
| } |
| } |
| |
| ast.AsyncMarker getAsyncMarker({bool isAsync: false, bool isStar: false}) { |
| return ast.AsyncMarker.values[(isAsync ? 2 : 0) + (isStar ? 1 : 0)]; |
| } |
| |
| ast.FunctionNode buildFunctionNode( |
| FormalParameterList formalParameters, FunctionBody body, |
| {TypeName returnType, |
| List<ast.TypeParameter> typeParameters, |
| ast.DartType inferredReturnType}) { |
| // TODO(asgerf): This will in many cases rebuild the interface built by |
| // TypeScope.buildFunctionInterface. |
| var positional = <ast.VariableDeclaration>[]; |
| var named = <ast.VariableDeclaration>[]; |
| int requiredParameterCount = 0; |
| var formals = formalParameters?.parameters ?? const <FormalParameter>[]; |
| for (var parameter in formals) { |
| var declaration = makeVariableDeclaration(parameter.element, |
| initializer: parameter is DefaultFormalParameter |
| ? buildOptionalTopLevelExpression(parameter.defaultValue) |
| : null, |
| type: buildType( |
| resolutionMap.elementDeclaredByFormalParameter(parameter).type)); |
| switch (parameter.kind) { |
| case ParameterKind.REQUIRED: |
| positional.add(declaration); |
| ++requiredParameterCount; |
| declaration.initializer = null; |
| break; |
| |
| case ParameterKind.POSITIONAL: |
| positional.add(declaration); |
| break; |
| |
| case ParameterKind.NAMED: |
| named.add(declaration); |
| break; |
| } |
| } |
| int offset = formalParameters?.offset ?? body.offset; |
| int endOffset = body.endToken.offset; |
| ast.AsyncMarker asyncMarker = |
| getAsyncMarker(isAsync: body.isAsynchronous, isStar: body.isGenerator); |
| return new ast.FunctionNode(buildOptionalFunctionBody(body), |
| typeParameters: typeParameters, |
| positionalParameters: positional, |
| namedParameters: named, |
| requiredParameterCount: requiredParameterCount, |
| returnType: buildOptionalTypeAnnotation(returnType) ?? |
| inferredReturnType ?? |
| const ast.DynamicType(), |
| asyncMarker: asyncMarker, |
| dartAsyncMarker: asyncMarker) |
| ..fileOffset = offset |
| ..fileEndOffset = endOffset; |
| } |
| |
| ast.Expression buildOptionalTopLevelExpression(Expression node) { |
| return node == null ? null : buildTopLevelExpression(node); |
| } |
| |
| ast.Expression buildTopLevelExpression(Expression node) { |
| try { |
| return _expressionBuilder.build(node); |
| } on _CompilationError catch (e) { |
| return buildThrowCompileTimeError(e.message); |
| } |
| } |
| |
| ast.Expression buildExpression(Expression node) { |
| return _expressionBuilder.build(node); |
| } |
| |
| ast.Expression buildOptionalExpression(Expression node) { |
| return node == null ? null : _expressionBuilder.build(node); |
| } |
| |
| Accessor buildLeftHandValue(Expression node) { |
| return _expressionBuilder.buildLeftHandValue(node); |
| } |
| |
| ast.Expression buildStringLiteral(Expression node) { |
| List<ast.Expression> parts = <ast.Expression>[]; |
| new StringLiteralPartBuilder(this, parts).build(node); |
| return parts.length == 1 && parts[0] is ast.StringLiteral |
| ? parts[0] |
| : new ast.StringConcatenation(parts); |
| } |
| |
| ast.Expression buildThis() { |
| return allowThis |
| ? new ast.ThisExpression() |
| : emitCompileTimeError(CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS); |
| } |
| |
| ast.Initializer buildInitializer(ConstructorInitializer node) { |
| try { |
| return new InitializerBuilder(this).build(node); |
| } on _CompilationError catch (_) { |
| return new ast.InvalidInitializer(); |
| } |
| } |
| |
| bool isFinal(Element element) { |
| return element is VariableElement && element.isFinal || |
| element is FunctionElement; |
| } |
| |
| bool isConst(Element element) { |
| return element is VariableElement && element.isConst; |
| } |
| |
| ast.VariableDeclaration getVariableReference(LocalElement element) { |
| return localVariables.putIfAbsent(element, () { |
| return new ast.VariableDeclaration(element.name, |
| isFinal: isFinal(element), |
| isConst: isConst(element))..fileOffset = element.nameOffset; |
| }); |
| } |
| |
| ast.DartType getInferredVariableType(Element element) { |
| if (!strongMode) return const ast.DynamicType(); |
| if (element is FunctionTypedElement) { |
| return buildType(element.type); |
| } else if (element is VariableElement) { |
| return buildType(element.type); |
| } else { |
| log.severe('Unexpected variable element: $element'); |
| return const ast.DynamicType(); |
| } |
| } |
| |
| ast.VariableDeclaration makeVariableDeclaration(LocalElement element, |
| {ast.DartType type, ast.Expression initializer, int equalsOffset}) { |
| var declaration = getVariableReference(element); |
| if (equalsOffset != null) declaration.fileEqualsOffset = equalsOffset; |
| declaration.type = type ?? getInferredVariableType(element); |
| if (initializer != null) { |
| declaration.initializer = initializer..parent = declaration; |
| } |
| return declaration; |
| } |
| |
| /// Returns true if [arguments] can be accepted by [target] |
| /// (not taking type checks into account). |
| bool areArgumentsCompatible( |
| FunctionTypedElement target, ast.Arguments arguments) { |
| var positionals = arguments.positional; |
| var parameters = target.parameters; |
| const required = ParameterKind.REQUIRED; // For avoiding long lines. |
| const named = ParameterKind.NAMED; |
| // If the first unprovided parameter is required, there are too few |
| // positional arguments. |
| if (positionals.length < parameters.length && |
| parameters[positionals.length].parameterKind == required) { |
| return false; |
| } |
| // If there are more positional arguments than parameters, or if the last |
| // positional argument corresponds to a named parameter, there are too many |
| // positional arguments. |
| if (positionals.length > parameters.length) return false; |
| if (positionals.isNotEmpty && |
| parameters[positionals.length - 1].parameterKind == named) { |
| return false; // Too many positional arguments. |
| } |
| if (arguments.named.isEmpty) return true; |
| int firstNamedParameter = positionals.length; |
| while (firstNamedParameter < parameters.length && |
| parameters[firstNamedParameter].parameterKind != ParameterKind.NAMED) { |
| ++firstNamedParameter; |
| } |
| namedLoop: |
| for (int i = 0; i < arguments.named.length; ++i) { |
| String name = arguments.named[i].name; |
| for (int j = firstNamedParameter; j < parameters.length; ++j) { |
| if (parameters[j].parameterKind == ParameterKind.NAMED && |
| parameters[j].name == name) { |
| continue namedLoop; |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /// Throws a NoSuchMethodError corresponding to a call to [memberName] on |
| /// [receiver] with the given [arguments]. |
| /// |
| /// If provided, [candiateTarget] provides the expected arity and argument |
| /// names for the best candidate target. |
| ast.Expression buildThrowNoSuchMethodError( |
| ast.Expression receiver, String memberName, ast.Arguments arguments, |
| {Element candidateTarget}) { |
| // TODO(asgerf): When we have better integration with patch files, use |
| // the internal constructor that provides a more detailed error message. |
| ast.Expression candidateArgumentNames; |
| if (candidateTarget is FunctionTypedElement) { |
| candidateArgumentNames = new ast.ListLiteral(candidateTarget.parameters |
| .map((p) => new ast.StringLiteral(p.name)) |
| .toList()); |
| } else { |
| candidateArgumentNames = new ast.NullLiteral(); |
| } |
| return new ast.Throw(new ast.ConstructorInvocation( |
| loader.getCoreClassConstructorReference('NoSuchMethodError'), |
| new ast.Arguments(<ast.Expression>[ |
| receiver, |
| new ast.SymbolLiteral(memberName), |
| new ast.ListLiteral(arguments.positional), |
| new ast.MapLiteral(arguments.named.map((arg) { |
| return new ast.MapEntry(new ast.SymbolLiteral(arg.name), arg.value); |
| }).toList()), |
| candidateArgumentNames |
| ]))); |
| } |
| |
| ast.Expression buildThrowCompileTimeError(String message) { |
| // The spec does not mandate a specific behavior in face of a compile-time |
| // error. We just throw a string. The VM throws an uncatchable exception |
| // for this case. |
| // TOOD(asgerf): Should we add uncatchable exceptions to kernel? |
| return new ast.Throw(new ast.StringLiteral(message)); |
| } |
| |
| ast.Expression buildThrowCompileTimeErrorFromCode(ErrorCode code, |
| [List arguments]) { |
| return buildThrowCompileTimeError(makeErrorMessage(code, arguments)); |
| } |
| |
| static final RegExp _errorMessagePattern = new RegExp(r'\{(\d+)\}'); |
| |
| String makeErrorMessage(ErrorCode error, [List arguments]) { |
| String message = error.message; |
| if (arguments != null) { |
| message = message.replaceAllMapped(_errorMessagePattern, (m) { |
| String numberString = m.group(1); |
| int index = int.parse(numberString); |
| return arguments[index]; |
| }); |
| } |
| return message; |
| } |
| |
| /// Throws an exception that will be caught at the function level, to replace |
| /// the entire function with a throw. |
| emitCompileTimeError(ErrorCode error, [List arguments]) { |
| throw new _CompilationError(makeErrorMessage(error, arguments)); |
| } |
| |
| ast.Expression buildThrowAbstractClassInstantiationError(String name) { |
| return new ast.Throw(new ast.ConstructorInvocation( |
| loader.getCoreClassConstructorReference( |
| 'AbstractClassInstantiationError'), |
| new ast.Arguments(<ast.Expression>[new ast.StringLiteral(name)]))); |
| } |
| |
| ast.Expression buildThrowFallThroughError() { |
| return new ast.Throw(new ast.ConstructorInvocation( |
| loader.getCoreClassConstructorReference('FallThroughError'), |
| new ast.Arguments.empty())); |
| } |
| |
| emitInvalidConstant([ErrorCode error]) { |
| error ??= CompileTimeErrorCode.INVALID_CONSTANT; |
| return emitCompileTimeError(error); |
| } |
| |
| internalError(String message) { |
| throw 'Internal error when compiling $location: $message'; |
| } |
| |
| unsupportedFeature(String feature) { |
| throw new _CompilationError('$feature is not supported'); |
| } |
| |
| ast.Expression buildAnnotation(Annotation annotation) { |
| Element element = annotation.element; |
| if (annotation.arguments == null) { |
| var target = resolveConcreteGet(element, null); |
| return target == null |
| ? new ast.InvalidExpression() |
| : new ast.StaticGet(target); |
| } else if (element is ConstructorElement && element.isConst) { |
| var target = resolveConstructor(element); |
| return target == null |
| ? new ast.InvalidExpression() |
| : new ast.ConstructorInvocation( |
| target, _expressionBuilder.buildArguments(annotation.arguments), |
| isConst: true); |
| } else { |
| return new ast.InvalidExpression(); |
| } |
| } |
| |
| void addTransformerFlag(int flags) { |
| // Overridden by MemberScope. |
| } |
| |
| /// True if the body of the given method must return nothing. |
| bool hasVoidReturn(ExecutableElement element) { |
| return (strongMode && element.returnType.isVoid) || |
| (element is PropertyAccessorElement && element.isSetter) || |
| element.name == '[]='; |
| } |
| |
| bool bodyHasVoidReturn(FunctionBody body) { |
| AstNode parent = body.parent; |
| return parent is MethodDeclaration && hasVoidReturn(parent.element) || |
| parent is FunctionDeclaration && hasVoidReturn(parent.element); |
| } |
| } |
| |
| /// A scope in which class type parameters are in scope, while not in scope |
| /// of a specific member. |
| class ClassScope extends ExpressionScope { |
| @override |
| bool get allowClassTypeParameters => true; |
| |
| ClassScope(ReferenceLevelLoader loader, ast.Library library) |
| : super(loader, library); |
| } |
| |
| /// Translates expressions, statements, and other constructs into [ast] nodes. |
| /// |
| /// Naming convention: |
| /// - `buildX` may not be given null as argument (it may crash the compiler). |
| /// - `buildOptionalX` returns null or an empty list if given null |
| /// - `buildMandatoryX` returns an invalid node if given null. |
| class MemberScope extends ExpressionScope { |
| /// A reference to the member currently being upgraded to body level. |
| final ast.Member currentMember; |
| |
| MemberScope(ReferenceLevelLoader loader, ast.Member currentMember) |
| : currentMember = currentMember, |
| super(loader, currentMember.enclosingLibrary) { |
| assert(currentMember != null); |
| } |
| |
| ast.Class get currentClass => currentMember.enclosingClass; |
| |
| bool get allowThis => _memberHasThis(currentMember); |
| |
| @override |
| bool get allowClassTypeParameters { |
| return currentMember.isInstanceMember || currentMember is ast.Constructor; |
| } |
| |
| /// Returns a string for debugging use, indicating the location of the member |
| /// being built. |
| String get location { |
| var library = currentMember.enclosingLibrary?.importUri ?? '<No Library>'; |
| var className = currentMember.enclosingClass == null |
| ? null |
| : (currentMember.enclosingClass?.name ?? '<Anonymous Class>'); |
| var member = |
| currentMember.name?.name ?? '<Anonymous ${currentMember.runtimeType}>'; |
| return [library, className, member].join('::'); |
| } |
| |
| bool _memberHasThis(ast.Member member) { |
| return member is ast.Procedure && !member.isStatic || |
| member is ast.Constructor; |
| } |
| |
| void addTransformerFlag(int flags) { |
| currentMember.transformerFlags |= flags; |
| } |
| } |
| |
| class LabelStack { |
| final List<String> labels; // Contains null for unlabeled targets. |
| final LabelStack next; |
| final List<ast.Statement> jumps = <ast.Statement>[]; |
| bool isSwitchTarget = false; |
| |
| LabelStack(String label, this.next) : labels = <String>[label]; |
| LabelStack.unlabeled(this.next) : labels = <String>[null]; |
| LabelStack.switchCase(String label, this.next) |
| : isSwitchTarget = true, |
| labels = <String>[label]; |
| LabelStack.many(this.labels, this.next); |
| } |
| |
| class StatementBuilder extends GeneralizingAstVisitor<ast.Statement> { |
| final ExpressionScope scope; |
| LabelStack breakStack, continueStack; |
| |
| StatementBuilder(this.scope, [this.breakStack, this.continueStack]); |
| |
| ast.Statement build(Statement node) { |
| ast.Statement result = node.accept(this); |
| result.fileOffset = _getOffset(node); |
| return result; |
| } |
| |
| ast.Statement buildOptional(Statement node) { |
| ast.Statement result = node?.accept(this); |
| result?.fileOffset = _getOffset(node); |
| return result; |
| } |
| |
| int _getOffset(AstNode node) { |
| return node.offset; |
| } |
| |
| ast.Statement buildInScope( |
| Statement node, LabelStack breakNode, LabelStack continueNode) { |
| var oldBreak = this.breakStack; |
| var oldContinue = this.continueStack; |
| breakStack = breakNode; |
| continueStack = continueNode; |
| var result = build(node); |
| this.breakStack = oldBreak; |
| this.continueStack = oldContinue; |
| return result; |
| } |
| |
| void buildBlockMember(Statement node, List<ast.Statement> output) { |
| if (node is LabeledStatement && |
| node.statement is VariableDeclarationStatement) { |
| // If a variable is labeled, its scope is part of the enclosing block. |
| LabeledStatement labeled = node; |
| node = labeled.statement; |
| } |
| if (node is VariableDeclarationStatement) { |
| VariableDeclarationList list = node.variables; |
| ast.DartType type = scope.buildOptionalTypeAnnotation(list.type); |
| for (VariableDeclaration decl in list.variables) { |
| LocalElement local = decl.element as dynamic; // Cross cast. |
| output.add(scope.makeVariableDeclaration(local, |
| type: type, |
| initializer: scope.buildOptionalExpression(decl.initializer), |
| equalsOffset: decl.equals?.offset)); |
| } |
| } else { |
| output.add(build(node)); |
| } |
| } |
| |
| ast.Statement makeBreakTarget(ast.Statement node, LabelStack stackNode) { |
| if (stackNode.jumps.isEmpty) return node; |
| var labeled = new ast.LabeledStatement(node); |
| for (var jump in stackNode.jumps) { |
| (jump as ast.BreakStatement).target = labeled; |
| } |
| return labeled; |
| } |
| |
| LabelStack findLabelTarget(String label, LabelStack stack) { |
| while (stack != null) { |
| if (stack.labels.contains(label)) return stack; |
| stack = stack.next; |
| } |
| return null; |
| } |
| |
| ast.Statement visitAssertStatement(AssertStatement node) { |
| return new ast.AssertStatement(scope.buildExpression(node.condition), |
| scope.buildOptionalExpression(node.message)); |
| } |
| |
| ast.Statement visitBlock(Block node) { |
| List<ast.Statement> statements = <ast.Statement>[]; |
| for (Statement statement in node.statements) { |
| buildBlockMember(statement, statements); |
| } |
| return new ast.Block(statements); |
| } |
| |
| ast.Statement visitBreakStatement(BreakStatement node) { |
| var stackNode = findLabelTarget(node.label?.name, breakStack); |
| if (stackNode == null) { |
| return node.label == null |
| ? scope.emitCompileTimeError(ParserErrorCode.BREAK_OUTSIDE_OF_LOOP) |
| : scope.emitCompileTimeError( |
| CompileTimeErrorCode.LABEL_UNDEFINED, [node.label.name]); |
| } |
| var result = new ast.BreakStatement(null); |
| stackNode.jumps.add(result); |
| return result; |
| } |
| |
| ast.Statement visitContinueStatement(ContinueStatement node) { |
| var stackNode = findLabelTarget(node.label?.name, continueStack); |
| if (stackNode == null) { |
| return node.label == null |
| ? scope.emitCompileTimeError(ParserErrorCode.CONTINUE_OUTSIDE_OF_LOOP) |
| : scope.emitCompileTimeError( |
| CompileTimeErrorCode.LABEL_UNDEFINED, [node.label.name]); |
| } |
| var result = stackNode.isSwitchTarget |
| ? new ast.ContinueSwitchStatement(null) |
| : new ast.BreakStatement(null); |
| stackNode.jumps.add(result); |
| return result; |
| } |
| |
| void addLoopLabels(Statement loop, LabelStack continueNode) { |
| AstNode parent = loop.parent; |
| if (parent is LabeledStatement) { |
| for (var label in parent.labels) { |
| continueNode.labels.add(label.label.name); |
| } |
| } |
| } |
| |
| ast.Statement visitDoStatement(DoStatement node) { |
| LabelStack breakNode = new LabelStack.unlabeled(breakStack); |
| LabelStack continueNode = new LabelStack.unlabeled(continueStack); |
| addLoopLabels(node, continueNode); |
| var body = buildInScope(node.body, breakNode, continueNode); |
| var loop = new ast.DoStatement(makeBreakTarget(body, continueNode), |
| scope.buildExpression(node.condition)); |
| return makeBreakTarget(loop, breakNode); |
| } |
| |
| ast.Statement visitWhileStatement(WhileStatement node) { |
| LabelStack breakNode = new LabelStack.unlabeled(breakStack); |
| LabelStack continueNode = new LabelStack.unlabeled(continueStack); |
| addLoopLabels(node, continueNode); |
| var body = buildInScope(node.body, breakNode, continueNode); |
| var loop = new ast.WhileStatement(scope.buildExpression(node.condition), |
| makeBreakTarget(body, continueNode)); |
| return makeBreakTarget(loop, breakNode); |
| } |
| |
| ast.Statement visitEmptyStatement(EmptyStatement node) { |
| return new ast.EmptyStatement(); |
| } |
| |
| ast.Statement visitExpressionStatement(ExpressionStatement node) { |
| return new ast.ExpressionStatement(scope.buildExpression(node.expression)); |
| } |
| |
| static String _getLabelName(Label label) { |
| return label.label.name; |
| } |
| |
| ast.Statement visitLabeledStatement(LabeledStatement node) { |
| // Only set up breaks here. Loops handle labeling on their own. |
| var breakNode = new LabelStack.many( |
| node.labels.map(_getLabelName).toList(), breakStack); |
| var body = buildInScope(node.statement, breakNode, continueStack); |
| return makeBreakTarget(body, breakNode); |
| } |
| |
| static bool isBreakingExpression(ast.Expression node) { |
| return node is ast.Throw || node is ast.Rethrow; |
| } |
| |
| static bool isBreakingStatement(ast.Statement node) { |
| return node is ast.BreakStatement || |
| node is ast.ContinueSwitchStatement || |
| node is ast.ReturnStatement || |
| node is ast.ExpressionStatement && |
| isBreakingExpression(node.expression); |
| } |
| |
| ast.Statement visitSwitchStatement(SwitchStatement node) { |
| // Group all cases into case blocks. Use parallel lists to collect the |
| // intermediate terms until we are ready to create the AST nodes. |
| LabelStack breakNode = new LabelStack.unlabeled(breakStack); |
| LabelStack continueNode = continueStack; |
| var cases = <ast.SwitchCase>[]; |
| var bodies = <List<Statement>>[]; |
| var labelToNode = <String, ast.SwitchCase>{}; |
| ast.SwitchCase currentCase = null; |
| for (var member in node.members) { |
| if (currentCase != null && currentCase.isDefault) { |
| var error = member is SwitchCase |
| ? ParserErrorCode.SWITCH_HAS_CASE_AFTER_DEFAULT_CASE |
| : ParserErrorCode.SWITCH_HAS_MULTIPLE_DEFAULT_CASES; |
| return scope.emitCompileTimeError(error); |
| } |
| if (currentCase == null) { |
| currentCase = new ast.SwitchCase(<ast.Expression>[], <int>[], null); |
| cases.add(currentCase); |
| } |
| if (member is SwitchCase) { |
| var expression = scope.buildExpression(member.expression); |
| currentCase.expressions.add(expression..parent = currentCase); |
| currentCase.expressionOffsets.add(expression.fileOffset); |
| } else { |
| currentCase.isDefault = true; |
| } |
| for (Label label in member.labels) { |
| continueNode = |
| new LabelStack.switchCase(label.label.name, continueNode); |
| labelToNode[label.label.name] = currentCase; |
| } |
| if (member.statements?.isNotEmpty ?? false) { |
| bodies.add(member.statements); |
| currentCase = null; |
| } |
| } |
| if (currentCase != null) { |
| // Close off a trailing block. |
| bodies.add(const <Statement>[]); |
| currentCase = null; |
| } |
| // Now that the label environment is set up, build the bodies. |
| var oldBreak = this.breakStack; |
| var oldContinue = this.continueStack; |
| this.breakStack = breakNode; |
| this.continueStack = continueNode; |
| for (int i = 0; i < cases.length; ++i) { |
| var blockNodes = <ast.Statement>[]; |
| for (var statement in bodies[i]) { |
| buildBlockMember(statement, blockNodes); |
| } |
| if (blockNodes.isEmpty || !isBreakingStatement(blockNodes.last)) { |
| if (i < cases.length - 1) { |
| blockNodes.add( |
| new ast.ExpressionStatement(scope.buildThrowFallThroughError())); |
| } else { |
| var jump = new ast.BreakStatement(null); |
| blockNodes.add(jump); |
| breakNode.jumps.add(jump); |
| } |
| } |
| cases[i].body = new ast.Block(blockNodes)..parent = cases[i]; |
| } |
| // Unwind the stack of case labels and bind their jumps to the case target. |
| while (continueNode != oldContinue) { |
| for (var jump in continueNode.jumps) { |
| (jump as ast.ContinueSwitchStatement).target = |
| labelToNode[continueNode.labels.first]; |
| } |
| continueNode = continueNode.next; |
| } |
| var expression = scope.buildExpression(node.expression); |
| var result = new ast.SwitchStatement(expression, cases); |
| this.breakStack = oldBreak; |
| this.continueStack = oldContinue; |
| return makeBreakTarget(result, breakNode); |
| } |
| |
| ast.Statement visitForStatement(ForStatement node) { |
| List<ast.VariableDeclaration> variables = <ast.VariableDeclaration>[]; |
| ast.Expression initialExpression; |
| if (node.variables != null) { |
| VariableDeclarationList list = node.variables; |
| var type = scope.buildOptionalTypeAnnotation(list.type); |
| for (var variable in list.variables) { |
| LocalElement local = variable.element as dynamic; // Cross cast. |
| variables.add(scope.makeVariableDeclaration(local, |
| initializer: scope.buildOptionalExpression(variable.initializer), |
| type: type, |
| equalsOffset: variable.equals?.offset)); |
| } |
| } else if (node.initialization != null) { |
| initialExpression = scope.buildExpression(node.initialization); |
| } |
| var breakNode = new LabelStack.unlabeled(breakStack); |
| var continueNode = new LabelStack.unlabeled(continueStack); |
| addLoopLabels(node, continueNode); |
| var body = buildInScope(node.body, breakNode, continueNode); |
| var loop = new ast.ForStatement( |
| variables, |
| scope.buildOptionalExpression(node.condition), |
| node.updaters.map(scope.buildExpression).toList(), |
| makeBreakTarget(body, continueNode)); |
| loop = makeBreakTarget(loop, breakNode); |
| if (initialExpression != null) { |
| return new ast.Block(<ast.Statement>[ |
| new ast.ExpressionStatement(initialExpression), |
| loop |
| ]); |
| } |
| return loop; |
| } |
| |
| DartType iterableElementType(DartType iterable) { |
| if (iterable is InterfaceType) { |
| var iterator = iterable.lookUpInheritedGetter('iterator')?.returnType; |
| if (iterator is InterfaceType) { |
| return iterator.lookUpInheritedGetter('current')?.returnType; |
| } |
| } |
| return null; |
| } |
| |
| DartType streamElementType(DartType stream) { |
| if (stream is InterfaceType) { |
| var class_ = stream.element; |
| if (class_.library.isDartAsync && |
| class_.name == 'Stream' && |
| stream.typeArguments.length == 1) { |
| return stream.typeArguments[0]; |
| } |
| } |
| return null; |
| } |
| |
| ast.Statement visitForEachStatement(ForEachStatement node) { |
| ast.VariableDeclaration variable; |
| Accessor leftHand; |
| if (node.loopVariable != null) { |
| DeclaredIdentifier loopVariable = node.loopVariable; |
| variable = scope.makeVariableDeclaration(loopVariable.element, |
| type: scope.buildOptionalTypeAnnotation(loopVariable.type)); |
| } else if (node.identifier != null) { |
| leftHand = scope.buildLeftHandValue(node.identifier); |
| variable = new ast.VariableDeclaration(null, isFinal: true); |
| if (scope.strongMode) { |
| var containerType = node.iterable.staticType; |
| DartType elementType = node.awaitKeyword != null |
| ? streamElementType(containerType) |
| : iterableElementType(containerType); |
| if (elementType != null) { |
| variable.type = scope.buildType(elementType); |
| } |
| } |
| } |
| var breakNode = new LabelStack.unlabeled(breakStack); |
| var continueNode = new LabelStack.unlabeled(continueStack); |
| addLoopLabels(node, continueNode); |
| var body = buildInScope(node.body, breakNode, continueNode); |
| if (leftHand != null) { |
| // Desugar |
| // |
| // for (x in e) BODY |
| // |
| // to |
| // |
| // for (var tmp in e) { |
| // x = tmp; |
| // BODY |
| // } |
| body = new ast.Block(<ast.Statement>[ |
| new ast.ExpressionStatement(leftHand |
| .buildAssignment(new ast.VariableGet(variable), voidContext: true)), |
| body |
| ]); |
| } |
| var loop = new ast.ForInStatement( |
| variable, |
| scope.buildExpression(node.iterable), |
| makeBreakTarget(body, continueNode), |
| isAsync: node.awaitKeyword != null)..fileOffset = node.offset; |
| return makeBreakTarget(loop, breakNode); |
| } |
| |
| ast.Statement visitIfStatement(IfStatement node) { |
| return new ast.IfStatement(scope.buildExpression(node.condition), |
| build(node.thenStatement), buildOptional(node.elseStatement)); |
| } |
| |
| ast.Statement visitReturnStatement(ReturnStatement node) { |
| return new ast.ReturnStatement( |
| scope.buildOptionalExpression(node.expression)); |
| } |
| |
| ast.Catch buildCatchClause(CatchClause node) { |
| var exceptionVariable = node.exceptionParameter == null |
| ? null |
| : scope.makeVariableDeclaration(node.exceptionParameter.staticElement); |
| var stackTraceVariable = node.stackTraceParameter == null |
| ? null |
| : scope.makeVariableDeclaration(node.stackTraceParameter.staticElement); |
| return new ast.Catch(exceptionVariable, build(node.body), |
| stackTrace: stackTraceVariable, |
| guard: scope.buildOptionalTypeAnnotation(node.exceptionType) ?? |
| const ast.DynamicType()); |
| } |
| |
| ast.Statement visitTryStatement(TryStatement node) { |
| ast.Statement statement = build(node.body); |
| if (node.catchClauses.isNotEmpty) { |
| statement = new ast.TryCatch( |
| statement, node.catchClauses.map(buildCatchClause).toList()); |
| } |
| if (node.finallyBlock != null) { |
| statement = new ast.TryFinally(statement, build(node.finallyBlock)); |
| } |
| return statement; |
| } |
| |
| ast.Statement visitVariableDeclarationStatement( |
| VariableDeclarationStatement node) { |
| // This is only reached when a variable is declared in non-block level, |
| // because visitBlock intercepts visits to its children. |
| // An example where we hit this case is: |
| // |
| // if (foo) var x = 5, y = x + 1; |
| // |
| // We wrap these in a block: |
| // |
| // if (foo) { |
| // var x = 5; |
| // var y = x + 1; |
| // } |
| // |
| // Note that the use of a block here is required by the kernel language, |
| // even if there is only one variable declaration. |
| List<ast.Statement> statements = <ast.Statement>[]; |
| buildBlockMember(node, statements); |
| return new ast.Block(statements); |
| } |
| |
| ast.Statement visitYieldStatement(YieldStatement node) { |
| return new ast.YieldStatement(scope.buildExpression(node.expression), |
| isYieldStar: node.star != null); |
| } |
| |
| ast.Statement visitFunctionDeclarationStatement( |
| FunctionDeclarationStatement node) { |
| var declaration = node.functionDeclaration; |
| var expression = declaration.functionExpression; |
| LocalElement element = declaration.element as dynamic; // Cross cast. |
| return new ast.FunctionDeclaration( |
| scope.makeVariableDeclaration(element, |
| type: scope.buildType(declaration.element.type)), |
| scope.buildFunctionNode(expression.parameters, expression.body, |
| typeParameters: scope.buildOptionalTypeParameterList( |
| expression.typeParameters, |
| strongModeOnly: true), |
| returnType: declaration.returnType))..fileOffset = node.offset; |
| } |
| |
| @override |
| visitStatement(Statement node) { |
| return scope.internalError('Unhandled statement ${node.runtimeType}'); |
| } |
| } |
| |
| class ExpressionBuilder |
| extends GeneralizingAstVisitor /* <ast.Expression | Accessor> */ { |
| final ExpressionScope scope; |
| ast.VariableDeclaration cascadeReceiver; |
| ExpressionBuilder(this.scope); |
| |
| ast.Expression build(Expression node) { |
| var result = node.accept(this); |
| if (result is Accessor) { |
| result = result.buildSimpleRead(); |
| } |
| // For some method invocations we have already set a file offset to |
| // override the default behavior of _getOffset. |
| if (node is! MethodInvocation || result.fileOffset < 0) { |
| result.fileOffset = _getOffset(node); |
| } |
| return result; |
| } |
| |
| int _getOffset(AstNode node) { |
| if (node is MethodInvocation) { |
| return node.methodName.offset; |
| } else if (node is InstanceCreationExpression) { |
| return node.constructorName.offset; |
| } else if (node is BinaryExpression) { |
| return node.operator.offset; |
| } else if (node is PrefixedIdentifier) { |
| return node.identifier.offset; |
| } else if (node is AssignmentExpression) { |
| return _getOffset(node.leftHandSide); |
| } else if (node is PropertyAccess) { |
| return node.propertyName.offset; |
| } else if (node is IsExpression) { |
| return node.isOperator.offset; |
| } else if (node is AsExpression) { |
| return node.asOperator.offset; |
| } else if (node is StringLiteral) { |
| // Use a catch-all for StringInterpolation and AdjacentStrings: |
| // the debugger stops at the end. |
| return node.end; |
| } else if (node is IndexExpression) { |
| return node.leftBracket.offset; |
| } |
| return node.offset; |
| } |
| |
| Accessor buildLeftHandValue(Expression node) { |
| var result = node.accept(this); |
| if (result is Accessor) { |
| return result; |
| } else { |
| return new ReadOnlyAccessor(result, ast.TreeNode.noOffset); |
| } |
| } |
| |
| ast.Expression visitAsExpression(AsExpression node) { |
| return new ast.AsExpression( |
| build(node.expression), scope.buildTypeAnnotation(node.type)); |
| } |
| |
| ast.Expression visitAssignmentExpression(AssignmentExpression node) { |
| bool voidContext = isInVoidContext(node); |
| String operator = node.operator.value(); |
| var leftHand = buildLeftHandValue(node.leftHandSide); |
| var rightHand = build(node.rightHandSide); |
| if (operator == '=') { |
| return leftHand.buildAssignment(rightHand, voidContext: voidContext); |
| } else if (operator == '??=') { |
| return leftHand.buildNullAwareAssignment( |
| rightHand, scope.buildType(node.staticType), |
| voidContext: voidContext); |
| } else { |
| // Cut off the trailing '='. |
| var name = new ast.Name(operator.substring(0, operator.length - 1)); |
| return leftHand.buildCompoundAssignment(name, rightHand, |
| offset: node.offset, |
| voidContext: voidContext, |
| interfaceTarget: scope.resolveInterfaceMethod(node.staticElement)); |
| } |
| } |
| |
| ast.Expression visitAwaitExpression(AwaitExpression node) { |
| return new ast.AwaitExpression(build(node.expression)); |
| } |
| |
| ast.Arguments buildSingleArgument(Expression node) { |
| return new ast.Arguments(<ast.Expression>[build(node)]); |
| } |
| |
| ast.Expression visitBinaryExpression(BinaryExpression node) { |
| String operator = node.operator.value(); |
| if (operator == '&&' || operator == '||') { |
| return new ast.LogicalExpression( |
| build(node.leftOperand), operator, build(node.rightOperand)); |
| } |
| if (operator == '??') { |
| ast.Expression leftOperand = build(node.leftOperand); |
| if (leftOperand is ast.VariableGet) { |
| return new ast.ConditionalExpression( |
| buildIsNull(leftOperand, offset: node.leftOperand.offset), |
| build(node.rightOperand), |
| new ast.VariableGet(leftOperand.variable), |
| scope.getInferredType(node)); |
| } else { |
| var variable = new ast.VariableDeclaration.forValue(leftOperand); |
| return new ast.Let( |
| variable, |
| new ast.ConditionalExpression( |
| buildIsNull(new ast.VariableGet(variable), |
| offset: leftOperand.fileOffset), |
| build(node.rightOperand), |
| new ast.VariableGet(variable), |
| scope.getInferredType(node))); |
| } |
| } |
| bool isNegated = false; |
| if (operator == '!=') { |
| isNegated = true; |
| operator = '=='; |
| } |
| ast.Expression expression; |
| if (node.leftOperand is SuperExpression) { |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| expression = new ast.SuperMethodInvocation( |
| new ast.Name(operator), |
| buildSingleArgument(node.rightOperand), |
| scope.resolveConcreteMethod(node.staticElement)); |
| } else { |
| expression = new ast.MethodInvocation( |
| build(node.leftOperand), |
| new ast.Name(operator), |
| buildSingleArgument(node.rightOperand), |
| scope.resolveInterfaceMethod(node.staticElement)); |
| } |
| return isNegated ? new ast.Not(expression) : expression; |
| } |
| |
| ast.Expression visitBooleanLiteral(BooleanLiteral node) { |
| return new ast.BoolLiteral(node.value); |
| } |
| |
| ast.Expression visitDoubleLiteral(DoubleLiteral node) { |
| return new ast.DoubleLiteral(node.value); |
| } |
| |
| ast.Expression visitIntegerLiteral(IntegerLiteral node) { |
| return new ast.IntLiteral(node.value); |
| } |
| |
| ast.Expression visitNullLiteral(NullLiteral node) { |
| return new ast.NullLiteral(); |
| } |
| |
| ast.Expression visitSimpleStringLiteral(SimpleStringLiteral node) { |
| return new ast.StringLiteral(node.value); |
| } |
| |
| ast.Expression visitStringLiteral(StringLiteral node) { |
| return scope.buildStringLiteral(node); |
| } |
| |
| static Object _getTokenValue(Token token) { |
| return token.value(); |
| } |
| |
| ast.Expression visitSymbolLiteral(SymbolLiteral node) { |
| String value = node.components.map(_getTokenValue).join('.'); |
| return new ast.SymbolLiteral(value); |
| } |
| |
| ast.Expression visitCascadeExpression(CascadeExpression node) { |
| var receiver = build(node.target); |
| // If receiver is a variable it would be tempting to reuse it, but it |
| // might be reassigned in one of the cascade sections. |
| var receiverVariable = new ast.VariableDeclaration.forValue(receiver, |
| type: scope.getInferredType(node.target)); |
| var oldReceiver = this.cascadeReceiver; |
| cascadeReceiver = receiverVariable; |
| ast.Expression result = new ast.VariableGet(receiverVariable); |
| for (var section in node.cascadeSections.reversed) { |
| var dummy = new ast.VariableDeclaration.forValue(build(section)); |
| result = new ast.Let(dummy, result); |
| } |
| cascadeReceiver = oldReceiver; |
| return new ast.Let(receiverVariable, result); |
| } |
| |
| ast.Expression makeCascadeReceiver() { |
| assert(cascadeReceiver != null); |
| return new ast.VariableGet(cascadeReceiver); |
| } |
| |
| ast.Expression visitConditionalExpression(ConditionalExpression node) { |
| return new ast.ConditionalExpression( |
| build(node.condition), |
| build(node.thenExpression), |
| build(node.elseExpression), |
| scope.getInferredType(node)); |
| } |
| |
| ast.Expression visitFunctionExpression(FunctionExpression node) { |
| return new ast.FunctionExpression(scope.buildFunctionNode( |
| node.parameters, node.body, |
| typeParameters: scope.buildOptionalTypeParameterList( |
| node.typeParameters, |
| strongModeOnly: true), |
| inferredReturnType: scope.getInferredReturnType(node))); |
| } |
| |
| ast.Arguments buildArguments(ArgumentList valueArguments, |
| {TypeArgumentList explicitTypeArguments, |
| List<ast.DartType> inferTypeArguments()}) { |
| var positional = <ast.Expression>[]; |
| var named = <ast.NamedExpression>[]; |
| for (var argument in valueArguments.arguments) { |
| if (argument is NamedExpression) { |
| named.add(new ast.NamedExpression( |
| argument.name.label.name, build(argument.expression))); |
| } else if (named.isNotEmpty) { |
| return scope.emitCompileTimeError( |
| ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT); |
| } else { |
| positional.add(build(argument)); |
| } |
| } |
| List<ast.DartType> typeArguments; |
| if (explicitTypeArguments != null) { |
| typeArguments = scope.buildTypeArgumentList(explicitTypeArguments); |
| } else if (inferTypeArguments != null) { |
| typeArguments = inferTypeArguments(); |
| } |
| return new ast.Arguments(positional, named: named, types: typeArguments); |
| } |
| |
| ast.Arguments buildArgumentsForInvocation(InvocationExpression node) { |
| if (scope.strongMode) { |
| return buildArguments(node.argumentList, |
| explicitTypeArguments: node.typeArguments, |
| inferTypeArguments: () => |
| scope.getInferredInvocationTypeArguments(node)); |
| } else { |
| return buildArguments(node.argumentList); |
| } |
| } |
| |
| static final ast.Name callName = new ast.Name('call'); |
| |
| ast.Expression visitFunctionExpressionInvocation( |
| FunctionExpressionInvocation node) { |
| return new ast.MethodInvocation( |
| build(node.function), |
| callName, |
| buildArgumentsForInvocation(node), |
| scope.resolveInterfaceFunctionCallOnType(node.function.staticType)); |
| } |
| |
| visitPrefixedIdentifier(PrefixedIdentifier node) { |
| switch (ElementKind.of(node.prefix.staticElement)) { |
| case ElementKind.CLASS: |
| case ElementKind.LIBRARY: |
| case ElementKind.PREFIX: |
| case ElementKind.IMPORT: |
| if (node.identifier.staticElement != null) { |
| // Should be resolved to a static access. |
| // Do not invoke 'build', because the identifier should be seen as a |
| // left-hand value or an expression depending on the context. |
| return visitSimpleIdentifier(node.identifier); |
| } |
| // Unresolved access on a class or library. |
| return scope.unresolvedAccess(node.identifier.name); |
| |
| case ElementKind.DYNAMIC: |
| case ElementKind.FUNCTION_TYPE_ALIAS: |
| case ElementKind.TYPE_PARAMETER: |
| // TODO: Check with the spec to see exactly when a type literal can be |
| // used in a property access without surrounding parentheses. |
| // For now, just fall through to the property access case. |
| |
| case ElementKind.FIELD: |
| case ElementKind.TOP_LEVEL_VARIABLE: |
| case ElementKind.FUNCTION: |
| case ElementKind.METHOD: |
| case ElementKind.GETTER: |
| case ElementKind.SETTER: |
| case ElementKind.LOCAL_VARIABLE: |
| case ElementKind.PARAMETER: |
| case ElementKind.ERROR: |
| Element element = node.identifier.staticElement; |
| Element auxiliary = node.identifier.auxiliaryElements?.staticElement; |
| return PropertyAccessor.make( |
| build(node.prefix), |
| scope.buildName(node.identifier), |
| scope.resolveInterfaceGet(element, auxiliary), |
| scope.resolveInterfaceSet(element, auxiliary)); |
| |
| case ElementKind.UNIVERSE: |
| case ElementKind.NAME: |
| case ElementKind.CONSTRUCTOR: |
| case ElementKind.EXPORT: |
| case ElementKind.LABEL: |
| default: |
| return scope.internalError( |
| 'Unexpected element kind: ${node.prefix.staticElement}'); |
| } |
| } |
| |
| bool isStatic(Element element) { |
| if (element is ClassMemberElement) { |
| return element.isStatic || element.enclosingElement == null; |
| } |
| if (element is PropertyAccessorElement) { |
| return element.isStatic || element.enclosingElement == null; |
| } |
| if (element is FunctionElement) { |
| return element.isStatic; |
| } |
| return false; |
| } |
| |
| visitSimpleIdentifier(SimpleIdentifier node) { |
| Element element = node.staticElement; |
| switch (ElementKind.of(element)) { |
| case ElementKind.CLASS: |
| case ElementKind.DYNAMIC: |
| case ElementKind.FUNCTION_TYPE_ALIAS: |
| case ElementKind.TYPE_PARAMETER: |
| return new ast.TypeLiteral(scope.buildTypeAnnotation(node)); |
| |
| case ElementKind.ERROR: // This covers the case where nothing was found. |
| if (!scope.allowThis) { |
| return scope.unresolvedAccess(node.name); |
| } |
| return PropertyAccessor.make( |
| scope.buildThis(), scope.buildName(node), null, null); |
| |
| case ElementKind.FIELD: |
| case ElementKind.TOP_LEVEL_VARIABLE: |
| case ElementKind.GETTER: |
| case ElementKind.SETTER: |
| case ElementKind.METHOD: |
| Element auxiliary = node.auxiliaryElements?.staticElement; |
| if (isStatic(element)) { |
| return scope.staticAccess(node.name, element, auxiliary); |
| } |
| if (!scope.allowThis) { |
| return scope.unresolvedAccess(node.name); |
| } |
| return PropertyAccessor.make( |
| scope.buildThis(), |
| scope.buildName(node), |
| scope.resolveInterfaceGet(element, auxiliary), |
| scope.resolveInterfaceSet(element, auxiliary)); |
| |
| case ElementKind.FUNCTION: |
| FunctionElement function = element; |
| if (isTopLevelFunction(function)) { |
| return scope.staticAccess(node.name, function); |
| } |
| if (function == function.library.loadLibraryFunction) { |
| return scope.unsupportedFeature('Deferred loading'); |
| } |
| return new VariableAccessor( |
| scope.getVariableReference(function), null, ast.TreeNode.noOffset); |
| |
| case ElementKind.LOCAL_VARIABLE: |
| case ElementKind.PARAMETER: |
| VariableElement variable = element; |
| var type = identical(node.staticType, variable.type) |
| ? null |
| : scope.buildType(node.staticType); |
| return new VariableAccessor( |
| scope.getVariableReference(element), type, ast.TreeNode.noOffset); |
| |
| case ElementKind.IMPORT: |
| case ElementKind.LIBRARY: |
| case ElementKind.PREFIX: |
| return scope.emitCompileTimeError( |
| CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, |
| [node.name]); |
| |
| case ElementKind.COMPILATION_UNIT: |
| case ElementKind.CONSTRUCTOR: |
| case ElementKind.EXPORT: |
| case ElementKind.LABEL: |
| case ElementKind.UNIVERSE: |
| case ElementKind.NAME: |
| default: |
| return scope.internalError('Unexpected element kind: $element'); |
| } |
| } |
| |
| visitIndexExpression(IndexExpression node) { |
| Element element = node.staticElement; |
| Element auxiliary = node.auxiliaryElements?.staticElement; |
| if (node.isCascaded) { |
| return IndexAccessor.make( |
| makeCascadeReceiver(), |
| build(node.index), |
| scope.resolveInterfaceIndexGet(element, auxiliary), |
| scope.resolveInterfaceIndexSet(element, auxiliary)); |
| } else if (node.target is SuperExpression) { |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| return new SuperIndexAccessor( |
| build(node.index), |
| scope.resolveConcreteIndexGet(element, auxiliary), |
| scope.resolveConcreteIndexSet(element, auxiliary), |
| ast.TreeNode.noOffset); |
| } else { |
| return IndexAccessor.make( |
| build(node.target), |
| build(node.index), |
| scope.resolveInterfaceIndexGet(element, auxiliary), |
| scope.resolveInterfaceIndexSet(element, auxiliary)); |
| } |
| } |
| |
| /// Follows any number of redirecting factories, returning the effective |
| /// target or `null` if a cycle is found. |
| /// |
| /// The returned element is a [Member] if the type arguments to the effective |
| /// target are different from the original arguments. |
| ConstructorElement getEffectiveFactoryTarget(ConstructorElement element) { |
| ConstructorElement anchor = null; |
| int n = 1; |
| while (element.isFactory && element.redirectedConstructor != null) { |
| element = element.redirectedConstructor; |
| var base = ReferenceScope.getBaseElement(element); |
| if (base == anchor) return null; // Cyclic redirection. |
| if (n & ++n == 0) { |
| anchor = base; |
| } |
| } |
| return element; |
| } |
| |
| /// Forces the list of type arguments to have the specified length. If the |
| /// length was changed, all type arguments are changed to `dynamic`. |
| void _coerceTypeArgumentArity(List<ast.DartType> typeArguments, int arity) { |
| if (typeArguments.length != arity) { |
| typeArguments.length = arity; |
| typeArguments.fillRange(0, arity, const ast.DynamicType()); |
| } |
| } |
| |
| ast.Expression visitInstanceCreationExpression( |
| InstanceCreationExpression node) { |
| ConstructorElement element = node.staticElement; |
| ClassElement classElement = element?.enclosingElement; |
| List<ast.DartType> inferTypeArguments() { |
| var inferredType = scope.getInferredType(node); |
| if (inferredType is ast.InterfaceType) { |
| return inferredType.typeArguments.toList(); |
| } |
| int numberOfTypeArguments = |
| classElement == null ? 0 : classElement.typeParameters.length; |
| return new List<ast.DartType>.filled( |
| numberOfTypeArguments, const ast.DynamicType(), |
| growable: true); |
| } |
| |
| var arguments = buildArguments(node.argumentList, |
| explicitTypeArguments: node.constructorName.type.typeArguments, |
| inferTypeArguments: inferTypeArguments); |
| ast.Expression noSuchMethodError() { |
| return node.isConst |
| ? scope.emitInvalidConstant() |
| : scope.buildThrowNoSuchMethodError( |
| new ast.NullLiteral(), '${node.constructorName}', arguments, |
| candidateTarget: element); |
| } |
| |
| if (element == null) { |
| return noSuchMethodError(); |
| } |
| assert(classElement != null); |
| var redirect = getEffectiveFactoryTarget(element); |
| if (redirect == null) { |
| return scope.buildThrowCompileTimeError( |
| CompileTimeErrorCode.RECURSIVE_FACTORY_REDIRECT.message); |
| } |
| if (redirect != element) { |
| ast.InterfaceType returnType = scope.buildType(redirect.returnType); |
| arguments.types |
| ..clear() |
| ..addAll(returnType.typeArguments); |
| element = redirect; |
| classElement = element.enclosingElement; |
| } |
| element = ReferenceScope.getBaseElement(element); |
| if (node.isConst && !element.isConst) { |
| return scope |
| .emitInvalidConstant(CompileTimeErrorCode.CONST_WITH_NON_CONST); |
| } |
| if (classElement.isEnum) { |
| return scope.emitCompileTimeError(CompileTimeErrorCode.INSTANTIATE_ENUM); |
| } |
| _coerceTypeArgumentArity( |
| arguments.types, classElement.typeParameters.length); |
| if (element.isFactory) { |
| ast.Member target = scope.resolveConcreteMethod(element); |
| if (target is ast.Procedure && |
| scope.areArgumentsCompatible(element, arguments)) { |
| return new ast.StaticInvocation(target, arguments, |
| isConst: node.isConst); |
| } else { |
| return noSuchMethodError(); |
| } |
| } |
| if (classElement.isAbstract) { |
| return node.isConst |
| ? scope.emitInvalidConstant() |
| : scope.buildThrowAbstractClassInstantiationError(classElement.name); |
| } |
| ast.Constructor constructor = scope.resolveConstructor(element); |
| if (constructor != null && |
| scope.areArgumentsCompatible(element, arguments)) { |
| return new ast.ConstructorInvocation(constructor, arguments, |
| isConst: node.isConst); |
| } else { |
| return noSuchMethodError(); |
| } |
| } |
| |
| ast.Expression visitIsExpression(IsExpression node) { |
| if (node.notOperator != null) { |
| // Put offset on the IsExpression for "is!" cases: |
| // As it is wrapped in a not, it won't get an offset otherwise. |
| return new ast.Not(new ast.IsExpression( |
| build(node.expression), scope.buildTypeAnnotation(node.type)) |
| ..fileOffset = _getOffset(node)); |
| } else { |
| return new ast.IsExpression( |
| build(node.expression), scope.buildTypeAnnotation(node.type)); |
| } |
| } |
| |
| /// Emit a method invocation, either as a direct call `o.f(x)` or decomposed |
| /// into a getter and function invocation `o.f.call(x)`. |
| ast.Expression buildDecomposableMethodInvocation(ast.Expression receiver, |
| ast.Name name, ast.Arguments arguments, Element targetElement) { |
| // Try to emit a typed call to an interface method. |
| ast.Procedure targetMethod = scope.resolveInterfaceMethod(targetElement); |
| if (targetMethod != null) { |
| return new ast.MethodInvocation(receiver, name, arguments, targetMethod); |
| } |
| // Try to emit a typed call to getter or field and call the returned |
| // function. |
| ast.Member targetGetter = scope.resolveInterfaceGet(targetElement, null); |
| if (targetGetter != null) { |
| return new ast.MethodInvocation( |
| new ast.PropertyGet(receiver, name, targetGetter), |
| callName, |
| arguments, |
| scope.resolveInterfaceFunctionCall(targetElement)); |
| } |
| // Emit a dynamic call. |
| return new ast.MethodInvocation(receiver, name, arguments); |
| } |
| |
| ast.Expression visitMethodInvocation(MethodInvocation node) { |
| Element element = node.methodName.staticElement; |
| if (element != null && element == element.library?.loadLibraryFunction) { |
| return scope.unsupportedFeature('Deferred loading'); |
| } |
| var target = node.target; |
| if (node.isCascaded) { |
| return buildDecomposableMethodInvocation( |
| makeCascadeReceiver(), |
| scope.buildName(node.methodName), |
| buildArgumentsForInvocation(node), |
| element); |
| } else if (target is SuperExpression) { |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| return new ast.SuperMethodInvocation( |
| scope.buildName(node.methodName), |
| buildArgumentsForInvocation(node), |
| scope.resolveConcreteMethod(element)); |
| } else if (isLocal(element)) { |
| // Set the offset directly: Normally the offset is at the start of the |
| // method, but in this case, because we insert a '.call', we want it at |
| // the end instead. |
| return new ast.MethodInvocation( |
| new ast.VariableGet(scope.getVariableReference(element)), |
| callName, |
| buildArgumentsForInvocation(node), |
| scope.resolveInterfaceFunctionCall(element)) |
| ..fileOffset = node.methodName.end; |
| } else if (isStaticMethod(element)) { |
| var method = scope.resolveConcreteMethod(element); |
| var arguments = buildArgumentsForInvocation(node); |
| if (method == null || !scope.areArgumentsCompatible(element, arguments)) { |
| return scope.buildThrowNoSuchMethodError( |
| new ast.NullLiteral(), node.methodName.name, arguments, |
| candidateTarget: element); |
| } |
| return new ast.StaticInvocation(method, arguments); |
| } else if (isStaticVariableOrGetter(element)) { |
| var method = scope.resolveConcreteGet(element, null); |
| if (method == null) { |
| return scope.buildThrowNoSuchMethodError( |
| new ast.NullLiteral(), node.methodName.name, new ast.Arguments([]), |
| candidateTarget: element); |
| } |
| // Set the offset directly: Normally the offset is at the start of the |
| // method, but in this case, because we insert a '.call', we want it at |
| // the end instead. |
| return new ast.MethodInvocation( |
| new ast.StaticGet(method), |
| callName, |
| buildArgumentsForInvocation(node), |
| scope.resolveInterfaceFunctionCall(element)) |
| ..fileOffset = node.methodName.end; |
| } else if (target == null && !scope.allowThis || |
| target is Identifier && target.staticElement is ClassElement || |
| target is Identifier && target.staticElement is PrefixElement) { |
| return scope.buildThrowNoSuchMethodError(new ast.NullLiteral(), |
| node.methodName.name, buildArgumentsForInvocation(node), |
| candidateTarget: element); |
| } else if (target == null) { |
| return buildDecomposableMethodInvocation( |
| scope.buildThis(), |
| scope.buildName(node.methodName), |
| buildArgumentsForInvocation(node), |
| element); |
| } else if (node.operator.value() == '?.') { |
| var receiver = makeOrReuseVariable(build(target)); |
| return makeLet( |
| receiver, |
| new ast.ConditionalExpression( |
| buildIsNull(new ast.VariableGet(receiver)), |
| new ast.NullLiteral(), |
| buildDecomposableMethodInvocation( |
| new ast.VariableGet(receiver), |
| scope.buildName(node.methodName), |
| buildArgumentsForInvocation(node), |
| element)..fileOffset = node.methodName.offset, |
| scope.buildType(node.staticType))); |
| } else { |
| return buildDecomposableMethodInvocation( |
| build(node.target), |
| scope.buildName(node.methodName), |
| buildArgumentsForInvocation(node), |
| element); |
| } |
| } |
| |
| ast.Expression visitNamedExpression(NamedExpression node) { |
| return scope.internalError('Unexpected named expression'); |
| } |
| |
| ast.Expression visitParenthesizedExpression(ParenthesizedExpression node) { |
| return build(node.expression); |
| } |
| |
| bool isInVoidContext(Expression node) { |
| AstNode parent = node.parent; |
| return parent is ForStatement && |
| (parent.updaters.contains(node) || parent.initialization == node) || |
| parent is ExpressionStatement || |
| parent is ExpressionFunctionBody && scope.bodyHasVoidReturn(parent); |
| } |
| |
| ast.Expression visitPostfixExpression(PostfixExpression node) { |
| String operator = node.operator.value(); |
| switch (operator) { |
| case '++': |
| case '--': |
| var leftHand = buildLeftHandValue(node.operand); |
| var binaryOperator = new ast.Name(operator[0]); |
| return leftHand.buildPostfixIncrement(binaryOperator, |
| offset: node.operator.offset, |
| voidContext: isInVoidContext(node), |
| interfaceTarget: scope.resolveInterfaceMethod(node.staticElement)); |
| |
| default: |
| return scope.internalError('Invalid postfix operator $operator'); |
| } |
| } |
| |
| ast.Expression visitPrefixExpression(PrefixExpression node) { |
| String operator = node.operator.value(); |
| switch (operator) { |
| case '-': |
| case '~': |
| var name = new ast.Name(operator == '-' ? 'unary-' : '~'); |
| if (node.operand is SuperExpression) { |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| return new ast.SuperMethodInvocation(name, new ast.Arguments.empty(), |
| scope.resolveConcreteMethod(node.staticElement)); |
| } |
| return new ast.MethodInvocation( |
| build(node.operand), |
| name, |
| new ast.Arguments.empty(), |
| scope.resolveInterfaceMethod(node.staticElement)); |
| |
| case '!': |
| return new ast.Not(build(node.operand)); |
| |
| case '++': |
| case '--': |
| var leftHand = buildLeftHandValue(node.operand); |
| var binaryOperator = new ast.Name(operator[0]); |
| return leftHand.buildPrefixIncrement(binaryOperator, |
| offset: node.offset, |
| interfaceTarget: scope.resolveInterfaceMethod(node.staticElement)); |
| |
| default: |
| return scope.internalError('Invalid prefix operator $operator'); |
| } |
| } |
| |
| visitPropertyAccess(PropertyAccess node) { |
| Element element = node.propertyName.staticElement; |
| Element auxiliary = node.propertyName.auxiliaryElements?.staticElement; |
| var getter = scope.resolveInterfaceGet(element, auxiliary); |
| var setter = scope.resolveInterfaceSet(element, auxiliary); |
| Expression target = node.target; |
| if (node.isCascaded) { |
| return PropertyAccessor.make(makeCascadeReceiver(), |
| scope.buildName(node.propertyName), getter, setter); |
| } else if (node.target is SuperExpression) { |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| return new SuperPropertyAccessor( |
| scope.buildName(node.propertyName), |
| scope.resolveConcreteGet(element, auxiliary), |
| scope.resolveConcreteSet(element, auxiliary), |
| ast.TreeNode.noOffset); |
| } else if (target is Identifier && target.staticElement is ClassElement) { |
| // Note that this case also covers null-aware static access on a class, |
| // which is equivalent to a regular static access. |
| return scope.staticAccess(node.propertyName.name, element, auxiliary); |
| } else if (node.operator.value() == '?.') { |
| return new NullAwarePropertyAccessor( |
| build(target), |
| scope.buildName(node.propertyName), |
| getter, |
| setter, |
| scope.buildType(node.staticType), |
| ast.TreeNode.noOffset); |
| } else { |
| return PropertyAccessor.make( |
| build(target), scope.buildName(node.propertyName), getter, setter); |
| } |
| } |
| |
| ast.Expression visitRethrowExpression(RethrowExpression node) { |
| return new ast.Rethrow(); |
| } |
| |
| ast.Expression visitSuperExpression(SuperExpression node) { |
| return scope |
| .emitCompileTimeError(CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT); |
| } |
| |
| ast.Expression visitThisExpression(ThisExpression node) { |
| return scope.buildThis(); |
| } |
| |
| ast.Expression visitThrowExpression(ThrowExpression node) { |
| return new ast.Throw(build(node.expression)); |
| } |
| |
| ast.Expression visitListLiteral(ListLiteral node) { |
| ast.DartType type = node.typeArguments?.arguments?.isNotEmpty ?? false |
| ? scope.buildTypeAnnotation(node.typeArguments.arguments[0]) |
| : scope.getInferredTypeArgument(node, 0); |
| return new ast.ListLiteral(node.elements.map(build).toList(), |
| typeArgument: type, isConst: node.constKeyword != null); |
| } |
| |
| ast.Expression visitMapLiteral(MapLiteral node) { |
| ast.DartType key, value; |
| if (node.typeArguments != null && node.typeArguments.arguments.length > 1) { |
| key = scope.buildTypeAnnotation(node.typeArguments.arguments[0]); |
| value = scope.buildTypeAnnotation(node.typeArguments.arguments[1]); |
| } else { |
| key = scope.getInferredTypeArgument(node, 0); |
| value = scope.getInferredTypeArgument(node, 1); |
| } |
| return new ast.MapLiteral(node.entries.map(buildMapEntry).toList(), |
| keyType: key, valueType: value, isConst: node.constKeyword != null); |
| } |
| |
| ast.MapEntry buildMapEntry(MapLiteralEntry node) { |
| return new ast.MapEntry(build(node.key), build(node.value)); |
| } |
| |
| ast.Expression visitExpression(Expression node) { |
| return scope.internalError('Unhandled expression ${node.runtimeType}'); |
| } |
| } |
| |
| class StringLiteralPartBuilder extends GeneralizingAstVisitor<Null> { |
| final ExpressionScope scope; |
| final List<ast.Expression> output; |
| StringLiteralPartBuilder(this.scope, this.output); |
| |
| void build(Expression node) { |
| node.accept(this); |
| } |
| |
| void buildInterpolationElement(InterpolationElement node) { |
| node.accept(this); |
| } |
| |
| visitSimpleStringLiteral(SimpleStringLiteral node) { |
| output.add(new ast.StringLiteral(node.value)); |
| } |
| |
| visitAdjacentStrings(AdjacentStrings node) { |
| node.strings.forEach(build); |
| } |
| |
| visitStringInterpolation(StringInterpolation node) { |
| node.elements.forEach(buildInterpolationElement); |
| } |
| |
| visitInterpolationString(InterpolationString node) { |
| output.add(new ast.StringLiteral(node.value)); |
| } |
| |
| visitInterpolationExpression(InterpolationExpression node) { |
| output.add(scope.buildExpression(node.expression)); |
| } |
| } |
| |
| class TypeAnnotationBuilder extends GeneralizingAstVisitor<ast.DartType> { |
| final TypeScope scope; |
| |
| TypeAnnotationBuilder(this.scope); |
| |
| ast.DartType build(AstNode node) { |
| return node.accept(this); |
| } |
| |
| List<ast.DartType> buildList(Iterable<AstNode> node) { |
| return node.map(build).toList(); |
| } |
| |
| /// Replace unbound type variables in [type] with 'dynamic' and convert |
| /// to an [ast.DartType]. |
| ast.DartType buildClosedTypeFromDartType(DartType type) { |
| return convertType(type, <TypeParameterElement>[]); |
| } |
| |
| /// Convert to an [ast.DartType] and keep type variables. |
| ast.DartType buildFromDartType(DartType type) { |
| return convertType(type, null); |
| } |
| |
| /// True if [parameter] should not be reified, because spec mode does not |
| /// currently reify generic method type parameters. |
| bool isUnreifiedTypeParameter(TypeParameterElement parameter) { |
| return !scope.strongMode && parameter.enclosingElement is! ClassElement; |
| } |
| |
| /// Converts [type] to an [ast.DartType], while replacing unbound type |
| /// variables with 'dynamic'. |
| /// |
| /// If [boundVariables] is null, no type variables are replaced, otherwise all |
| /// type variables except those in [boundVariables] are replaced. In other |
| /// words, it represents the bound variables, or "all variables" if omitted. |
| ast.DartType convertType( |
| DartType type, List<TypeParameterElement> boundVariables) { |
| if (type is TypeParameterType) { |
| if (isUnreifiedTypeParameter(type.element)) { |
| return const ast.DynamicType(); |
| } |
| if (boundVariables == null || boundVariables.contains(type)) { |
| var typeParameter = scope.tryGetTypeParameterReference(type.element); |
| if (typeParameter == null) { |
| // The analyzer sometimes gives us a type parameter that was not |
| // bound anywhere. Make sure we do not emit a dangling reference. |
| if (type.element.bound != null) { |
| return convertType(type.element.bound, []); |
| } |
| return const ast.DynamicType(); |
| } |
| if (!scope.allowClassTypeParameters && |
| typeParameter.parent is ast.Class) { |
| return const ast.InvalidType(); |
| } |
| return new ast.TypeParameterType(typeParameter); |
| } else { |
| return const ast.DynamicType(); |
| } |
| } else if (type is InterfaceType) { |
| var classNode = scope.getClassReference(type.element); |
| if (type.typeArguments.length == 0) { |
| return classNode.rawType; |
| } |
| if (type.typeArguments.length != classNode.typeParameters.length) { |
| log.warning('Type parameter arity error in $type'); |
| return const ast.InvalidType(); |
| } |
| return new ast.InterfaceType( |
| classNode, convertTypeList(type.typeArguments, boundVariables)); |
| } else if (type is FunctionType) { |
| // TODO: Avoid infinite recursion in case of illegal circular typedef. |
| boundVariables?.addAll(type.typeParameters); |
| var positionals = |
| concatenate(type.normalParameterTypes, type.optionalParameterTypes); |
| var result = new ast.FunctionType( |
| convertTypeList(positionals, boundVariables), |
| convertType(type.returnType, boundVariables), |
| typeParameters: |
| convertTypeParameterList(type.typeFormals, boundVariables), |
| namedParameters: |
| convertTypeMap(type.namedParameterTypes, boundVariables), |
| requiredParameterCount: type.normalParameterTypes.length); |
| boundVariables?.removeRange( |
| boundVariables.length - type.typeParameters.length, |
| boundVariables.length); |
| return result; |
| } else if (type.isUndefined) { |
| log.warning('Unresolved type found in ${scope.location}'); |
| return const ast.InvalidType(); |
| } else if (type.isVoid) { |
| return const ast.VoidType(); |
| } else if (type.isDynamic) { |
| return const ast.DynamicType(); |
| } else { |
| log.severe('Unexpected DartType: $type'); |
| return const ast.InvalidType(); |
| } |
| } |
| |
| static Iterable/*<E>*/ concatenate/*<E>*/( |
| Iterable/*<E>*/ x, Iterable/*<E>*/ y) => |
| <Iterable<dynamic/*=E*/ >>[x, y].expand((z) => z); |
| |
| ast.TypeParameter convertTypeParameter(TypeParameterElement typeParameter, |
| List<TypeParameterElement> boundVariables) { |
| return scope.makeTypeParameter(typeParameter, |
| bound: typeParameter.bound == null |
| ? scope.defaultTypeParameterBound |
| : convertType(typeParameter.bound, boundVariables)); |
| } |
| |
| List<ast.TypeParameter> convertTypeParameterList( |
| Iterable<TypeParameterElement> typeParameters, |
| List<TypeParameterElement> boundVariables) { |
| if (typeParameters.isEmpty) return const <ast.TypeParameter>[]; |
| return typeParameters |
| .map((tp) => convertTypeParameter(tp, boundVariables)) |
| .toList(); |
| } |
| |
| List<ast.DartType> convertTypeList( |
| Iterable<DartType> types, List<TypeParameterElement> boundVariables) { |
| if (types.isEmpty) return const <ast.DartType>[]; |
| return types.map((t) => convertType(t, boundVariables)).toList(); |
| } |
| |
| List<ast.NamedType> convertTypeMap( |
| Map<String, DartType> types, List<TypeParameterElement> boundVariables) { |
| if (types.isEmpty) return const <ast.NamedType>[]; |
| List<ast.NamedType> result = <ast.NamedType>[]; |
| types.forEach((name, type) { |
| result.add(new ast.NamedType(name, convertType(type, boundVariables))); |
| }); |
| sortAndRemoveDuplicates(result); |
| return result; |
| } |
| |
| ast.DartType visitSimpleIdentifier(SimpleIdentifier node) { |
| Element element = node.staticElement; |
| switch (ElementKind.of(element)) { |
| case ElementKind.CLASS: |
| return scope.getClassReference(element).rawType; |
| |
| case ElementKind.DYNAMIC: |
| return const ast.DynamicType(); |
| |
| case ElementKind.FUNCTION_TYPE_ALIAS: |
| FunctionTypeAliasElement functionType = element; |
| return buildClosedTypeFromDartType(functionType.type); |
| |
| case ElementKind.TYPE_PARAMETER: |
| var typeParameter = scope.getTypeParameterReference(element); |
| if (!scope.allowClassTypeParameters && |
| typeParameter.parent is ast.Class) { |
| return const ast.InvalidType(); |
| } |
| if (isUnreifiedTypeParameter(element)) { |
| return const ast.DynamicType(); |
| } |
| return new ast.TypeParameterType(typeParameter); |
| |
| case ElementKind.COMPILATION_UNIT: |
| case ElementKind.CONSTRUCTOR: |
| case ElementKind.EXPORT: |
| case ElementKind.IMPORT: |
| case ElementKind.LABEL: |
| case ElementKind.LIBRARY: |
| case ElementKind.PREFIX: |
| case ElementKind.UNIVERSE: |
| case ElementKind.ERROR: // This covers the case where nothing was found. |
| case ElementKind.FIELD: |
| case ElementKind.TOP_LEVEL_VARIABLE: |
| case ElementKind.GETTER: |
| case ElementKind.SETTER: |
| case ElementKind.METHOD: |
| case ElementKind.LOCAL_VARIABLE: |
| case ElementKind.PARAMETER: |
| case ElementKind.FUNCTION: |
| case ElementKind.NAME: |
| default: |
| log.severe('Invalid type annotation: $element'); |
| return const ast.InvalidType(); |
| } |
| } |
| |
| visitPrefixedIdentifier(PrefixedIdentifier node) { |
| return build(node.identifier); |
| } |
| |
| visitTypeName(TypeName node) { |
| return buildFromDartType(node.type); |
| } |
| |
| visitNode(AstNode node) { |
| log.severe('Unexpected type annotation: $node'); |
| return new ast.InvalidType(); |
| } |
| } |
| |
| class InitializerBuilder extends GeneralizingAstVisitor<ast.Initializer> { |
| final MemberScope scope; |
| |
| InitializerBuilder(this.scope); |
| |
| ast.Initializer build(ConstructorInitializer node) { |
| return node.accept(this); |
| } |
| |
| visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| var target = scope.resolveField(node.fieldName.staticElement); |
| if (target == null) { |
| return new ast.InvalidInitializer(); |
| } |
| return new ast.FieldInitializer( |
| target, scope.buildExpression(node.expression)); |
| } |
| |
| visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| var target = scope.resolveConstructor(node.staticElement); |
| if (target == null) { |
| return new ast.InvalidInitializer(); |
| } |
| scope.addTransformerFlag(TransformerFlag.superCalls); |
| return new ast.SuperInitializer( |
| target, scope._expressionBuilder.buildArguments(node.argumentList)); |
| } |
| |
| visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) { |
| var target = scope.resolveConstructor(node.staticElement); |
| if (target == null) { |
| return new ast.InvalidInitializer(); |
| } |
| return new ast.RedirectingInitializer( |
| target, scope._expressionBuilder.buildArguments(node.argumentList)); |
| } |
| |
| visitNode(AstNode node) { |
| log.severe('Unexpected constructor initializer: ${node.runtimeType}'); |
| return new ast.InvalidInitializer(); |
| } |
| } |
| |
| /// Brings a class from hierarchy level to body level. |
| // |
| // TODO(asgerf): Error recovery during class construction is currently handled |
| // locally, but this can in theory break global invariants in the kernel IR. |
| // To safely compile code with compile-time errors, we may need a recovery |
| // pass to enforce all kernel invariants before it is given to the backend. |
| class ClassBodyBuilder extends GeneralizingAstVisitor<Null> { |
| final ClassScope scope; |
| final ExpressionScope annotationScope; |
| final ast.Class currentClass; |
| final ClassElement element; |
| ast.Library get currentLibrary => currentClass.enclosingLibrary; |
| |
| ClassBodyBuilder( |
| ReferenceLevelLoader loader, ast.Class currentClass, this.element) |
| : this.currentClass = currentClass, |
| scope = new ClassScope(loader, currentClass.enclosingLibrary), |
| annotationScope = |
| new ExpressionScope(loader, currentClass.enclosingLibrary); |
| |
| void build(CompilationUnitMember node) { |
| if (node == null) { |
| buildBrokenClass(); |
| return; |
| } |
| node.accept(this); |
| } |
| |
| /// Builds an empty class for broken classes that have no AST. |
| /// |
| /// This should only be used to recover from a compile-time error. |
| void buildBrokenClass() { |
| currentClass.name = element.name; |
| currentClass.supertype = scope.getRootClassReference().asRawSupertype; |
| currentClass.constructors.add( |
| new ast.Constructor(new ast.FunctionNode(new ast.InvalidStatement())) |
| ..fileOffset = element.nameOffset); |
| } |
| |
| void addAnnotations(List<Annotation> annotations) { |
| // Class type parameters are not in scope in the annotation list. |
| for (var annotation in annotations) { |
| currentClass.addAnnotation(annotationScope.buildAnnotation(annotation)); |
| } |
| } |
| |
| void _buildMemberBody(ast.Member member, Element element, AstNode node) { |
| new MemberBodyBuilder(scope.loader, member, element).build(node); |
| } |
| |
| /// True if the given class member should not be emitted, and does not |
| /// correspond to any Kernel member. |
| /// |
| /// This is true for redirecting factories with a resolved target. These are |
| /// always bypassed at the call site. |
| bool _isIgnoredMember(ClassMember node) { |
| if (node is ConstructorDeclaration && node.factoryKeyword != null) { |
| var element = resolutionMap.elementDeclaredByConstructorDeclaration(node); |
| return element.redirectedConstructor != null && |
| (element.isSynthetic || scope.loader.ignoreRedirectingFactories); |
| } else { |
| return false; |
| } |
| } |
| |
| visitClassDeclaration(ClassDeclaration node) { |
| addAnnotations(node.metadata); |
| ast.Class classNode = currentClass; |
| assert(classNode.members.isEmpty); // All members will be added here. |
| |
| bool foundConstructor = false; |
| for (var member in node.members) { |
| if (_isIgnoredMember(member)) continue; |
| if (member is FieldDeclaration) { |
| for (var variable in member.fields.variables) { |
| // Ignore fields inserted through error recovery. |
| if (variable.isSynthetic || variable.length == 0) continue; |
| var field = scope.getMemberReference(variable.element); |
| classNode.addMember(field); |
| _buildMemberBody(field, variable.element, variable); |
| } |
| } else { |
| var memberNode = scope.getMemberReference(member.element); |
| classNode.addMember(memberNode); |
| _buildMemberBody(memberNode, member.element, member); |
| if (member is ConstructorDeclaration) { |
| foundConstructor = true; |
| } |
| } |
| } |
| |
| if (!foundConstructor) { |
| var defaultConstructor = scope.findDefaultConstructor(node.element); |
| if (defaultConstructor != null) { |
| assert(defaultConstructor.enclosingElement == node.element); |
| if (!defaultConstructor.isSynthetic) { |
| throw 'Non-synthetic default constructor not in list of members. ' |
| '$node $element $defaultConstructor'; |
| } |
| var memberNode = scope.getMemberReference(defaultConstructor); |
| classNode.addMember(memberNode); |
| buildDefaultConstructor(memberNode, defaultConstructor); |
| } |
| } |
| |
| addDefaultInstanceFieldInitializers(classNode); |
| } |
| |
| void buildDefaultConstructor( |
| ast.Constructor constructor, ConstructorElement element) { |
| var function = constructor.function; |
| function.body = new ast.EmptyStatement()..parent = function; |
| var class_ = element.enclosingElement; |
| if (class_.supertype != null) { |
| // DESIGN TODO: If the super class is a mixin application, we will link to |
| // a constructor not in the immediate super class. This is a problem due |
| // to the fact that mixed-in fields come with initializers which need to |
| // be executed by a constructor. The mixin transformer takes care of |
| // this by making forwarding constructors and the super initializers will |
| // be rewritten to use them (see `transformations/mixin_full_resolution`). |
| var superConstructor = |
| scope.findDefaultConstructor(class_.supertype.element); |
| var target = scope.resolveConstructor(superConstructor); |
| if (target == null) { |
| constructor.initializers |
| .add(new ast.InvalidInitializer()..parent = constructor); |
| } else { |
| var arguments = new ast.Arguments.empty(); |
| constructor.initializers.add( |
| new ast.SuperInitializer(target, arguments)..parent = constructor); |
| } |
| } |
| } |
| |
| /// Adds initializers to instance fields that are have no initializer and are |
| /// not initialized by all constructors in the class. |
| void addDefaultInstanceFieldInitializers(ast.Class node) { |
| List<ast.Field> uninitializedFields = new List<ast.Field>(); |
| for (var field in node.fields) { |
| if (field.initializer != null || field.isStatic) continue; |
| uninitializedFields.add(field); |
| } |
| if (uninitializedFields.isEmpty) return; |
| constructorLoop: |
| for (var constructor in node.constructors) { |
| var remainingFields = uninitializedFields.toSet(); |
| for (var initializer in constructor.initializers) { |
| if (initializer is ast.FieldInitializer) { |
| remainingFields.remove(initializer.field); |
| } else if (initializer is ast.RedirectingInitializer) { |
| // The target constructor will be checked in another iteration. |
| continue constructorLoop; |
| } |
| } |
| for (var field in remainingFields) { |
| if (field.initializer == null) { |
| field.initializer = new ast.NullLiteral()..parent = field; |
| } |
| } |
| } |
| } |
| |
| /// True for the `values` field of an `enum` class. |
| static bool _isValuesField(FieldElement field) => field.name == 'values'; |
| |
| /// True for the `index` field of an `enum` class. |
| static bool _isIndexField(FieldElement field) => field.name == 'index'; |
| |
| visitEnumDeclaration(EnumDeclaration node) { |
| addAnnotations(node.metadata); |
| ast.Class classNode = currentClass; |
| |
| var intType = scope.loader.getCoreClassReference('int').rawType; |
| var indexFieldElement = element.fields.firstWhere(_isIndexField); |
| ast.Field indexField = scope.getMemberReference(indexFieldElement); |
| indexField.type = intType; |
| classNode.addMember(indexField); |
| |
| var stringType = scope.loader.getCoreClassReference('String').rawType; |
| ast.Field nameField = new ast.Field( |
| new ast.Name('_name', scope.currentLibrary), |
| type: stringType, |
| isFinal: true, |
| fileUri: classNode.fileUri); |
| classNode.addMember(nameField); |
| |
| var indexParameter = new ast.VariableDeclaration('index', type: intType); |
| var nameParameter = new ast.VariableDeclaration('name', type: stringType); |
| var function = new ast.FunctionNode(new ast.EmptyStatement(), |
| positionalParameters: [indexParameter, nameParameter]); |
| var superConstructor = scope.loader.getRootClassConstructorReference(); |
| var constructor = new ast.Constructor(function, |
| name: new ast.Name(''), |
| isConst: true, |
| initializers: [ |
| new ast.FieldInitializer( |
| indexField, new ast.VariableGet(indexParameter)), |
| new ast.FieldInitializer( |
| nameField, new ast.VariableGet(nameParameter)), |
| new ast.SuperInitializer(superConstructor, new ast.Arguments.empty()) |
| ])..fileOffset = element.nameOffset; |
| classNode.addMember(constructor); |
| |
| int index = 0; |
| var enumConstantFields = <ast.Field>[]; |
| for (var constant in node.constants) { |
| ast.Field field = scope.getMemberReference(constant.element); |
| field.initializer = new ast.ConstructorInvocation( |
| constructor, |
| new ast.Arguments([ |
| new ast.IntLiteral(index), |
| new ast.StringLiteral('${classNode.name}.${field.name.name}') |
| ]), |
| isConst: true)..parent = field; |
| field.type = classNode.rawType; |
| classNode.addMember(field); |
| ++index; |
| enumConstantFields.add(field); |
| } |
| |
| // Add the 'values' field. |
| var valuesFieldElement = element.fields.firstWhere(_isValuesField); |
| ast.Field valuesField = scope.getMemberReference(valuesFieldElement); |
| var enumType = classNode.rawType; |
| valuesField.type = new ast.InterfaceType( |
| scope.loader.getCoreClassReference('List'), <ast.DartType>[enumType]); |
| valuesField.initializer = new ast.ListLiteral( |
| enumConstantFields.map(_makeStaticGet).toList(), |
| isConst: true, |
| typeArgument: enumType)..parent = valuesField; |
| classNode.addMember(valuesField); |
| |
| // Add the 'toString()' method. |
| var body = new ast.ReturnStatement( |
| new ast.DirectPropertyGet(new ast.ThisExpression(), nameField)); |
| var toStringFunction = new ast.FunctionNode(body, returnType: stringType); |
| var toStringMethod = new ast.Procedure( |
| new ast.Name('toString'), ast.ProcedureKind.Method, toStringFunction, |
| fileUri: classNode.fileUri); |
| classNode.addMember(toStringMethod); |
| } |
| |
| visitClassTypeAlias(ClassTypeAlias node) { |
| addAnnotations(node.metadata); |
| assert(node.withClause != null && node.withClause.mixinTypes.isNotEmpty); |
| ast.Class classNode = currentClass; |
| for (var constructor in element.constructors) { |
| var constructorNode = scope.getMemberReference(constructor); |
| classNode.addMember(constructorNode); |
| buildMixinConstructor(constructorNode, constructor); |
| } |
| } |
| |
| void buildMixinConstructor( |
| ast.Constructor constructor, ConstructorElement element) { |
| var function = constructor.function; |
| function.body = new ast.EmptyStatement()..parent = function; |
| // Call the corresponding constructor in super class. |
| ClassElement classElement = element.enclosingElement; |
| var targetConstructor = classElement.supertype.element.constructors |
| .firstWhere((c) => c.name == element.name); |
| var positionalArguments = constructor.function.positionalParameters |
| .map(_makeVariableGet) |
| .toList(); |
| var namedArguments = constructor.function.namedParameters |
| .map(_makeNamedExpressionFrom) |
| .toList(); |
| constructor.initializers.add(new ast.SuperInitializer( |
| scope.getMemberReference(targetConstructor), |
| new ast.Arguments(positionalArguments, named: namedArguments)) |
| ..parent = constructor); |
| } |
| |
| visitNode(AstNode node) { |
| throw 'Unsupported class declaration: ${node.runtimeType}'; |
| } |
| } |
| |
| /// Brings a member from reference level to body level. |
| class MemberBodyBuilder extends GeneralizingAstVisitor<Null> { |
| final MemberScope scope; |
| final Element element; |
| ast.Member get currentMember => scope.currentMember; |
| |
| MemberBodyBuilder( |
| ReferenceLevelLoader loader, ast.Member member, this.element) |
| : scope = new MemberScope(loader |