| // Copyright (c) 2015, 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 dart2js.parser.partial_elements; |
| |
| import '../common.dart'; |
| import '../common/resolution.dart' show ParsingContext, Resolution; |
| import '../elements/resolution_types.dart' show ResolutionDynamicType; |
| import '../elements/elements.dart' |
| show |
| CompilationUnitElement, |
| Element, |
| ElementKind, |
| GetterElement, |
| MetadataAnnotation, |
| SetterElement, |
| STATE_NOT_STARTED, |
| STATE_DONE; |
| import '../elements/modelx.dart' |
| show |
| BaseFunctionElementX, |
| ClassElementX, |
| ConstructorElementX, |
| DeclarationSite, |
| ElementX, |
| GetterElementX, |
| MetadataAnnotationX, |
| MethodElementX, |
| SetterElementX, |
| TypedefElementX, |
| VariableList; |
| import '../elements/visitor.dart' show ElementVisitor; |
| import 'package:front_end/src/fasta/scanner.dart' show Token; |
| import 'package:front_end/src/fasta/scanner.dart' as Tokens show EOF_TOKEN; |
| import '../tree/tree.dart'; |
| import 'package:front_end/src/fasta/parser.dart' |
| show ClassMemberParser, Listener, Parser, ParserError; |
| import 'member_listener.dart' show MemberListener; |
| import 'node_listener.dart' show NodeListener; |
| |
| class ClassElementParser extends ClassMemberParser { |
| ClassElementParser(Listener listener) : super(listener); |
| |
| Token parseFormalParameters(Token token, {bool inFunctionType: false}) { |
| return skipFormalParameters(token); |
| } |
| } |
| |
| abstract class PartialElement implements DeclarationSite { |
| Token beginToken; |
| Token endToken; |
| |
| bool hasParseError = false; |
| |
| bool get isMalformed => hasParseError; |
| |
| DeclarationSite get declarationSite => this; |
| } |
| |
| abstract class PartialFunctionMixin implements BaseFunctionElementX { |
| FunctionExpression cachedNode; |
| Modifiers get modifiers; |
| Token beginToken; |
| Token getOrSet; |
| Token endToken; |
| |
| /** |
| * The position is computed in the constructor using [findMyName]. Computing |
| * it on demand fails in case tokens are GC'd. |
| */ |
| Token _position; |
| |
| void init(Token beginToken, Token getOrSet, Token endToken) { |
| this.beginToken = beginToken; |
| this.getOrSet = getOrSet; |
| this.endToken = endToken; |
| _position = ElementX.findNameToken( |
| beginToken, |
| modifiers.isFactory || isGenerativeConstructor, |
| name, |
| enclosingElement.name); |
| } |
| |
| bool get hasNode => cachedNode != null; |
| |
| FunctionExpression get node { |
| assert(invariant(this, cachedNode != null, |
| message: "Node has not been computed for $this.")); |
| return cachedNode; |
| } |
| |
| FunctionExpression parseNode(ParsingContext parsing) { |
| if (cachedNode != null) return cachedNode; |
| parseFunction(Parser p) { |
| if (isClassMember && modifiers.isFactory) { |
| p.parseFactoryMethod(beginToken); |
| } else { |
| p.parseFunction(beginToken, getOrSet); |
| } |
| } |
| |
| cachedNode = parse(parsing, this, declarationSite, parseFunction); |
| return cachedNode; |
| } |
| |
| Token get position => _position; |
| |
| void reusePartialFunctionMixin() { |
| cachedNode = null; |
| } |
| |
| DeclarationSite get declarationSite; |
| } |
| |
| abstract class PartialFunctionElement |
| implements PartialElement, PartialFunctionMixin { |
| factory PartialFunctionElement(String name, Token beginToken, Token getOrSet, |
| Token endToken, Modifiers modifiers, Element enclosingElement, |
| {bool hasBody: true}) { |
| if (getOrSet == null) { |
| return new PartialMethodElement( |
| name, beginToken, endToken, modifiers, enclosingElement, |
| hasBody: hasBody); |
| } else if (identical(getOrSet.stringValue, 'get')) { |
| return new PartialGetterElement( |
| name, beginToken, getOrSet, endToken, modifiers, enclosingElement, |
| hasBody: hasBody); |
| } else { |
| assert(identical(getOrSet.stringValue, 'set')); |
| return new PartialSetterElement( |
| name, beginToken, getOrSet, endToken, modifiers, enclosingElement, |
| hasBody: hasBody); |
| } |
| } |
| |
| PartialFunctionElement copyWithEnclosing(Element enclosing); |
| } |
| |
| class PartialMethodElement extends MethodElementX |
| with PartialElement, PartialFunctionMixin |
| implements PartialFunctionElement { |
| PartialMethodElement(String name, Token beginToken, Token endToken, |
| Modifiers modifiers, Element enclosing, |
| {bool hasBody: true}) |
| : super(name, ElementKind.FUNCTION, modifiers, enclosing, hasBody) { |
| init(beginToken, null, endToken); |
| } |
| |
| void reuseElement() { |
| super.reuseElement(); |
| reusePartialFunctionMixin(); |
| } |
| |
| PartialMethodElement copyWithEnclosing(Element enclosing) { |
| return new PartialMethodElement( |
| name, beginToken, endToken, modifiers, enclosing, |
| hasBody: hasBody); |
| } |
| } |
| |
| class PartialGetterElement extends GetterElementX |
| with PartialElement, PartialFunctionMixin |
| implements GetterElement, PartialFunctionElement { |
| PartialGetterElement(String name, Token beginToken, Token getToken, |
| Token endToken, Modifiers modifiers, Element enclosing, |
| {bool hasBody: true}) |
| : super(name, modifiers, enclosing, hasBody) { |
| init(beginToken, getToken, endToken); |
| } |
| |
| @override |
| SetterElement get setter => abstractField.setter; |
| |
| void reuseElement() { |
| super.reuseElement(); |
| reusePartialFunctionMixin(); |
| } |
| |
| PartialGetterElement copyWithEnclosing(Element enclosing) { |
| return new PartialGetterElement( |
| name, beginToken, getOrSet, endToken, modifiers, enclosing, |
| hasBody: hasBody); |
| } |
| } |
| |
| class PartialSetterElement extends SetterElementX |
| with PartialElement, PartialFunctionMixin |
| implements SetterElement, PartialFunctionElement { |
| PartialSetterElement(String name, Token beginToken, Token setToken, |
| Token endToken, Modifiers modifiers, Element enclosing, |
| {bool hasBody: true}) |
| : super(name, modifiers, enclosing, hasBody) { |
| init(beginToken, setToken, endToken); |
| } |
| |
| @override |
| GetterElement get getter => abstractField.getter; |
| |
| void reuseElement() { |
| super.reuseElement(); |
| reusePartialFunctionMixin(); |
| } |
| |
| PartialSetterElement copyWithEnclosing(Element enclosing) { |
| return new PartialSetterElement( |
| name, beginToken, getOrSet, endToken, modifiers, enclosing, |
| hasBody: hasBody); |
| } |
| } |
| |
| // TODO(johnniwinther): Create [PartialGenerativeConstructor] and |
| // [PartialFactoryConstructor] subclasses and make this abstract. |
| class PartialConstructorElement extends ConstructorElementX |
| with PartialElement, PartialFunctionMixin { |
| PartialConstructorElement(String name, Token beginToken, Token endToken, |
| ElementKind kind, Modifiers modifiers, Element enclosing) |
| : super(name, kind, modifiers, enclosing) { |
| init(beginToken, null, endToken); |
| } |
| |
| void reuseElement() { |
| super.reuseElement(); |
| reusePartialFunctionMixin(); |
| } |
| } |
| |
| class PartialFieldList extends VariableList with PartialElement { |
| PartialFieldList( |
| Token beginToken, Token endToken, Modifiers modifiers, bool hasParseError) |
| : super(modifiers) { |
| super.beginToken = beginToken; |
| super.endToken = endToken; |
| super.hasParseError = hasParseError; |
| } |
| |
| VariableDefinitions parseNode(Element element, ParsingContext parsing) { |
| if (definitions != null) return definitions; |
| DiagnosticReporter reporter = parsing.reporter; |
| reporter.withCurrentElement(element, () { |
| definitions = parse(parsing, element, declarationSite, |
| (Parser parser) => parser.parseMember(beginToken)); |
| |
| if (!hasParseError && |
| !definitions.modifiers.isVar && |
| !definitions.modifiers.isFinal && |
| !definitions.modifiers.isConst && |
| definitions.type == null && |
| !definitions.isErroneous) { |
| reporter.reportErrorMessage(definitions, MessageKind.GENERIC, { |
| 'text': 'A field declaration must start with var, final, ' |
| 'const, or a type annotation.' |
| }); |
| } |
| }); |
| return definitions; |
| } |
| |
| computeType(Element element, Resolution resolution) { |
| if (type != null) return type; |
| // TODO(johnniwinther): Compute this in the resolver. |
| VariableDefinitions node = parseNode(element, resolution.parsingContext); |
| if (node.type != null) { |
| type = resolution.reporter.withCurrentElement(element, () { |
| return resolution.resolveTypeAnnotation(element, node.type); |
| }); |
| } else { |
| type = const ResolutionDynamicType(); |
| } |
| assert(type != null); |
| return type; |
| } |
| } |
| |
| class PartialTypedefElement extends TypedefElementX with PartialElement { |
| PartialTypedefElement( |
| String name, Element enclosing, Token beginToken, Token endToken) |
| : super(name, enclosing) { |
| this.beginToken = beginToken; |
| this.endToken = endToken; |
| } |
| |
| Token get token => beginToken; |
| |
| Node parseNode(ParsingContext parsing) { |
| if (cachedNode != null) return cachedNode; |
| cachedNode = parse(parsing, this, declarationSite, |
| (p) => p.parseTopLevelDeclaration(token)); |
| return cachedNode; |
| } |
| |
| Token get position => findMyName(token); |
| } |
| |
| /// A [MetadataAnnotation] which is constructed on demand. |
| class PartialMetadataAnnotation extends MetadataAnnotationX |
| implements PartialElement { |
| Token beginToken; // TODO(ahe): Make this final when issue 22065 is fixed. |
| |
| final Token tokenAfterEndToken; |
| |
| Expression cachedNode; |
| |
| bool hasParseError = false; |
| |
| PartialMetadataAnnotation(this.beginToken, this.tokenAfterEndToken); |
| |
| bool get isMalformed => hasParseError; |
| |
| DeclarationSite get declarationSite => this; |
| |
| Token get endToken { |
| Token token = beginToken; |
| while (token.kind != Tokens.EOF_TOKEN) { |
| if (identical(token.next, tokenAfterEndToken)) break; |
| token = token.next; |
| } |
| assert(token != null); |
| return token; |
| } |
| |
| void set endToken(_) { |
| throw new UnsupportedError("endToken="); |
| } |
| |
| Node parseNode(ParsingContext parsing) { |
| if (cachedNode != null) return cachedNode; |
| var metadata = parse(parsing, annotatedElement, declarationSite, |
| (p) => p.parseMetadata(beginToken)); |
| if (metadata is Metadata) { |
| cachedNode = metadata.expression; |
| return cachedNode; |
| } else { |
| assert(metadata is ErrorNode); |
| return metadata; |
| } |
| } |
| |
| bool get hasNode => cachedNode != null; |
| |
| Node get node { |
| assert(invariant(this, hasNode)); |
| return cachedNode; |
| } |
| } |
| |
| class PartialClassElement extends ClassElementX with PartialElement { |
| ClassNode cachedNode; |
| |
| PartialClassElement( |
| String name, Token beginToken, Token endToken, Element enclosing, int id) |
| : super(name, enclosing, id, STATE_NOT_STARTED) { |
| this.beginToken = beginToken; |
| this.endToken = endToken; |
| } |
| |
| void set supertypeLoadState(int state) { |
| assert(state == STATE_NOT_STARTED || state == supertypeLoadState + 1); |
| assert(state <= STATE_DONE); |
| super.supertypeLoadState = state; |
| } |
| |
| void set resolutionState(int state) { |
| assert(state == STATE_NOT_STARTED || state == resolutionState + 1); |
| assert(state <= STATE_DONE); |
| super.resolutionState = state; |
| } |
| |
| bool get hasNode => cachedNode != null; |
| |
| ClassNode get node { |
| assert(invariant(this, cachedNode != null, |
| message: "Node has not been computed for $this.")); |
| return cachedNode; |
| } |
| |
| ClassNode parseNode(ParsingContext parsing) { |
| if (cachedNode != null) return cachedNode; |
| DiagnosticReporter reporter = parsing.reporter; |
| reporter.withCurrentElement(this, () { |
| parsing.measure(() { |
| MemberListener listener = new MemberListener( |
| parsing.getScannerOptionsFor(this), reporter, this); |
| Parser parser = new ClassElementParser(listener); |
| try { |
| Token token = parser.parseTopLevelDeclaration(beginToken); |
| assert(identical(token, endToken.next)); |
| cachedNode = listener.popNode(); |
| assert(invariant( |
| reporter.spanFromToken(beginToken), listener.nodes.isEmpty, |
| message: "Non-empty listener stack: ${listener.nodes}")); |
| } on ParserError { |
| // TODO(ahe): Often, a ParserError is thrown while parsing the class |
| // body. This means that the stack actually contains most of the |
| // information synthesized below. Consider rewriting the parser so |
| // endClassDeclaration is called before parsing the class body. |
| Identifier name = new Identifier(findMyName(beginToken)); |
| NodeList typeParameters = null; |
| Node supertype = null; |
| NodeList interfaces = listener.makeNodeList(0, null, null, ","); |
| Token extendsKeyword = null; |
| NodeList body = listener.makeNodeList(0, beginToken, endToken, null); |
| cachedNode = new ClassNode( |
| Modifiers.EMPTY, |
| name, |
| typeParameters, |
| supertype, |
| interfaces, |
| beginToken, |
| extendsKeyword, |
| body, |
| endToken); |
| hasParseError = true; |
| } |
| }); |
| if (isPatched) { |
| parsing.parsePatchClass(patch); |
| } |
| }); |
| return cachedNode; |
| } |
| |
| Token get position => beginToken; |
| |
| // TODO(johnniwinther): Ensure that modifiers are always available. |
| Modifiers get modifiers => |
| cachedNode != null ? cachedNode.modifiers : Modifiers.EMPTY; |
| |
| accept(ElementVisitor visitor, arg) { |
| return visitor.visitClassElement(this, arg); |
| } |
| |
| PartialClassElement copyWithEnclosing(CompilationUnitElement enclosing) { |
| return new PartialClassElement(name, beginToken, endToken, enclosing, id); |
| } |
| } |
| |
| Node parse(ParsingContext parsing, ElementX element, PartialElement partial, |
| doParse(Parser parser)) { |
| DiagnosticReporter reporter = parsing.reporter; |
| return parsing.measure(() { |
| return reporter.withCurrentElement(element, () { |
| CompilationUnitElement unit = element.compilationUnit; |
| NodeListener listener = new NodeListener( |
| parsing.getScannerOptionsFor(element), reporter, unit); |
| listener.memberErrors = listener.memberErrors.prepend(false); |
| try { |
| if (partial.hasParseError) { |
| listener.suppressParseErrors = true; |
| } |
| doParse(new Parser(listener)); |
| } on ParserError catch (e) { |
| partial.hasParseError = true; |
| return new ErrorNode(element.position, e.kind, e.arguments); |
| } |
| Node node = listener.popNode(); |
| assert(listener.nodes.isEmpty); |
| return node; |
| }); |
| }); |
| } |