| // 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. |
| |
| /// Encapsulates how to invoke the analyzer resolver and overrides how it |
| /// computes types on expressions to use our restricted set of types. |
| library dev_compiler.src.checker.resolver; |
| |
| import 'package:analyzer/analyzer.dart'; |
| import 'package:analyzer/src/generated/ast.dart'; |
| import 'package:analyzer/src/generated/element.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/source.dart' show Source; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/generated/static_type_analyzer.dart'; |
| import 'package:analyzer/src/generated/utilities_collection.dart' |
| show DirectedGraph; |
| import 'package:logging/logging.dart' as logger; |
| |
| import '../../strong_mode.dart' show StrongModeOptions; |
| import '../utils.dart'; |
| |
| final _log = new logger.Logger('dev_compiler.src.resolver'); |
| |
| /// A [LibraryResolver] that performs inference on top-levels and fields based |
| /// on the value of the initializer, and on fields and methods based on |
| /// overridden members in super classes. |
| class LibraryResolverWithInference extends LibraryResolver { |
| final StrongModeOptions _options; |
| |
| LibraryResolverWithInference(context, this._options) : super(context); |
| |
| @override |
| void resolveReferencesAndTypes() { |
| _resolveVariableReferences(); |
| |
| // Run resolution in two stages, skipping method bodies first, so we can run |
| // type-inference before we fully analyze methods. |
| var visitors = _createVisitors(); |
| _resolveEverything(visitors); |
| _runInference(visitors); |
| |
| visitors.values.forEach((v) => v.skipMethodBodies = false); |
| _resolveEverything(visitors); |
| } |
| |
| // Note: this was split from _resolveReferencesAndTypesInLibrary so we do it |
| // only once. |
| void _resolveVariableReferences() { |
| for (Library library in resolvedLibraries) { |
| for (Source source in library.compilationUnitSources) { |
| library.getAST(source).accept(new VariableResolverVisitor( |
| library.libraryElement, source, typeProvider, library.errorListener, |
| nameScope: library.libraryScope)); |
| } |
| } |
| } |
| |
| // Note: this was split from _resolveReferencesAndTypesInLibrary so we can do |
| // resolution in pieces. |
| Map<Source, RestrictedResolverVisitor> _createVisitors() { |
| var visitors = <Source, RestrictedResolverVisitor>{}; |
| for (Library library in resolvedLibraries) { |
| for (Source source in library.compilationUnitSources) { |
| var visitor = new RestrictedResolverVisitor( |
| library, source, typeProvider, _options); |
| visitors[source] = visitor; |
| } |
| } |
| return visitors; |
| } |
| |
| /// Runs the resolver on the entire library cycle. |
| void _resolveEverything(Map<Source, RestrictedResolverVisitor> visitors) { |
| for (Library library in resolvedLibraries) { |
| for (Source source in library.compilationUnitSources) { |
| library.getAST(source).accept(visitors[source]); |
| } |
| } |
| } |
| |
| _runInference(Map<Source, RestrictedResolverVisitor> visitors) { |
| var globalsAndStatics = <VariableDeclaration>[]; |
| var classes = <ClassDeclaration>[]; |
| |
| // Extract top-level members that are const, statics, or classes. |
| for (Library library in resolvedLibraries) { |
| for (Source source in library.compilationUnitSources) { |
| CompilationUnit ast = library.getAST(source); |
| for (var declaration in ast.declarations) { |
| if (declaration is TopLevelVariableDeclaration) { |
| globalsAndStatics.addAll(declaration.variables.variables); |
| } else if (declaration is ClassDeclaration) { |
| classes.add(declaration); |
| for (var member in declaration.members) { |
| if (member is FieldDeclaration && |
| (member.fields.isConst || member.isStatic)) { |
| globalsAndStatics.addAll(member.fields.variables); |
| } |
| } |
| } |
| } |
| } |
| } |
| _inferGlobalsAndStatics(globalsAndStatics, visitors); |
| _inferInstanceFields(classes, visitors); |
| } |
| |
| _inferGlobalsAndStatics(List<VariableDeclaration> globalsAndStatics, |
| Map<Source, RestrictedResolverVisitor> visitors) { |
| var elementToDeclaration = {}; |
| for (var c in globalsAndStatics) { |
| elementToDeclaration[c.element] = c; |
| } |
| var constGraph = new DirectedGraph<VariableDeclaration>(); |
| globalsAndStatics.forEach(constGraph.addNode); |
| for (var c in globalsAndStatics) { |
| for (var e in _VarExtractor.extract(c.initializer)) { |
| // Note: declaration is null for variables that come from other strongly |
| // connected components. |
| var declaration = elementToDeclaration[e]; |
| if (declaration != null) constGraph.addEdge(c, declaration); |
| } |
| } |
| |
| for (var component in constGraph.computeTopologicalSort()) { |
| component.forEach((v) => _reanalyzeVar(visitors, v)); |
| _inferVariableFromInitializer(component); |
| } |
| } |
| |
| _inferInstanceFields(List<ClassDeclaration> classes, |
| Map<Source, RestrictedResolverVisitor> visitors) { |
| // First propagate what was inferred from globals to all instance fields. |
| |
| // TODO(sigmund): also do a fine-grain propagation between fields. We want |
| // infer-by-override to take precedence, so we would have to include |
| // classes in the dependency graph and ensure that fields depend on their |
| // class, and classes depend on superclasses. |
| classes |
| .expand((c) => c.members.where(_isInstanceField)) |
| .expand((f) => f.fields.variables) |
| .forEach((v) => _reanalyzeVar(visitors, v)); |
| |
| // Track types in this strongly connected component, ensure we visit |
| // supertypes before subtypes. |
| var typeToDeclaration = <InterfaceType, ClassDeclaration>{}; |
| classes.forEach((c) => typeToDeclaration[c.element.type] = c); |
| var seen = new Set<InterfaceType>(); |
| visit(ClassDeclaration cls) { |
| var element = cls.element; |
| var type = element.type; |
| if (seen.contains(type)) return; |
| seen.add(type); |
| for (var supertype in element.allSupertypes) { |
| var supertypeClass = typeToDeclaration[supertype]; |
| if (supertypeClass != null) visit(supertypeClass); |
| } |
| |
| // Infer field types from overrides first, otherwise from initializers. |
| var pending = new Set<VariableDeclaration>(); |
| cls.members |
| .where(_isInstanceField) |
| .forEach((f) => _inferFieldTypeFromOverride(f, pending)); |
| if (pending.isNotEmpty) _inferVariableFromInitializer(pending); |
| |
| // Infer return-types and param-types from overrides |
| cls.members |
| .where((m) => m is MethodDeclaration && !m.isStatic) |
| .forEach(_inferMethodTypesFromOverride); |
| } |
| classes.forEach(visit); |
| } |
| |
| void _reanalyzeVar(Map<Source, RestrictedResolverVisitor> visitors, |
| VariableDeclaration variable) { |
| if (variable.initializer == null) return; |
| var visitor = visitors[(variable.root as CompilationUnit).element.source]; |
| visitor.reanalyzeInitializer(variable); |
| } |
| |
| static bool _isInstanceField(f) => |
| f is FieldDeclaration && !f.isStatic && !f.fields.isConst; |
| |
| /// Attempts to infer the type on [field] from overridden fields or getters if |
| /// a type was not specified. If no type could be inferred, but it contains an |
| /// initializer, we add it to [pending] so we can try to infer it using the |
| /// initializer type instead. |
| void _inferFieldTypeFromOverride( |
| FieldDeclaration field, Set<VariableDeclaration> pending) { |
| var variables = field.fields; |
| for (var variable in variables.variables) { |
| var varElement = variable.element as FieldElement; |
| if (!varElement.type.isDynamic || variables.type != null) continue; |
| var getter = varElement.getter; |
| // Note: type will be null only when there are no overrides. When some |
| // override's type was not specified and couldn't be inferred, the type |
| // here will be dynamic. |
| var enclosingElement = varElement.enclosingElement; |
| var type = searchTypeFor(enclosingElement.type, getter); |
| |
| // Infer from the RHS when there are no overrides. |
| if (type == null) { |
| if (variable.initializer != null) pending.add(variable); |
| continue; |
| } |
| |
| // When field is final and overridden getter is dynamic, we can infer from |
| // the RHS without breaking subtyping rules (return type is covariant). |
| if (type.returnType.isDynamic) { |
| if (variables.isFinal && variable.initializer != null) { |
| pending.add(variable); |
| } |
| continue; |
| } |
| |
| // Use type from the override. |
| var newType = type.returnType; |
| varElement.type = newType; |
| varElement.getter.returnType = newType; |
| if (!varElement.isFinal) varElement.setter.parameters[0].type = newType; |
| } |
| } |
| |
| void _inferMethodTypesFromOverride(MethodDeclaration method) { |
| var methodElement = method.element; |
| if (methodElement is! MethodElement && |
| methodElement is! PropertyAccessorElement) return; |
| |
| var enclosingElement = methodElement.enclosingElement as ClassElement; |
| FunctionType type = null; |
| |
| // Infer the return type if omitted |
| if (methodElement.returnType.isDynamic && method.returnType == null) { |
| type = searchTypeFor(enclosingElement.type, methodElement); |
| if (type == null) return; |
| if (!type.returnType.isDynamic) { |
| methodElement.returnType = type.returnType; |
| } |
| } |
| |
| // Infer parameter types if omitted |
| if (method.parameters == null) return; |
| var parameters = method.parameters.parameters; |
| var length = parameters.length; |
| for (int i = 0; i < length; ++i) { |
| var parameter = parameters[i]; |
| if (parameter is DefaultFormalParameter) parameter = parameter.parameter; |
| if (parameter is SimpleFormalParameter && parameter.type == null) { |
| type = type ?? searchTypeFor(enclosingElement.type, methodElement); |
| if (type == null) return; |
| if (type.parameters.length > i && !type.parameters[i].type.isDynamic) { |
| parameter.element.type = type.parameters[i].type; |
| } |
| } |
| } |
| } |
| |
| void _inferVariableFromInitializer(Iterable<VariableDeclaration> variables) { |
| for (var variable in variables) { |
| var declaration = variable.parent as VariableDeclarationList; |
| // Only infer on variables that don't have any declared type. |
| if (declaration.type != null) continue; |
| var initializer = variable.initializer; |
| if (initializer == null) continue; |
| var type = initializer.staticType; |
| if (type == null || type.isDynamic || type.isBottom) continue; |
| var element = variable.element as PropertyInducingElement; |
| // Note: it's ok to update the type here, since initializer.staticType |
| // is already computed for all declarations in the library cycle. The |
| // new types will only be propagated on a second run of the |
| // ResolverVisitor. |
| element.type = type; |
| element.getter.returnType = type; |
| if (!element.isFinal && !element.isConst) { |
| element.setter.parameters[0].type = type; |
| } |
| } |
| } |
| } |
| |
| /// Extracts the [VariableElement]s used in an initializer expression. |
| class _VarExtractor extends RecursiveAstVisitor { |
| final elements = <VariableElement>[]; |
| visitSimpleIdentifier(SimpleIdentifier node) { |
| var e = node.staticElement; |
| if (e is PropertyAccessorElement) elements.add(e.variable); |
| } |
| |
| static List<VariableElement> extract(Expression initializer) { |
| if (initializer == null) return const []; |
| var extractor = new _VarExtractor(); |
| initializer.accept(extractor); |
| return extractor.elements; |
| } |
| } |
| |
| /// Overrides the default [ResolverVisitor] to support type inference in |
| /// [LibraryResolverWithInference] above. |
| /// |
| /// Before inference, this visitor is used to resolve top-levels, classes, and |
| /// fields, but nothing within method bodies. After inference, this visitor is |
| /// used again to step into method bodies and complete resolution as a second |
| /// phase. |
| class RestrictedResolverVisitor extends ResolverVisitor { |
| final TypeProvider _typeProvider; |
| |
| /// Whether to skip resolution within method bodies. |
| bool skipMethodBodies = true; |
| |
| /// State of the resolver at the point a field or variable was declared. |
| final _stateAtDeclaration = <AstNode, _ResolverState>{}; |
| |
| /// Internal tracking of whether a node was skipped while visiting, for |
| /// example, if it contained a function expression with a function body. |
| bool _nodeWasSkipped = false; |
| |
| /// Internal state, whether we are revisiting an initializer, so we minimize |
| /// the work being done elsewhere. |
| bool _revisiting = false; |
| |
| /// Initializers that have been visited, reanalyzed, and for which no node was |
| /// internally skipped. These initializers are fully resolved and don't need |
| /// to be re-resolved on a sunsequent pass. |
| final _visitedInitializers = new Set<VariableDeclaration>(); |
| |
| RestrictedResolverVisitor(Library library, Source source, |
| TypeProvider typeProvider, StrongModeOptions options) |
| : _typeProvider = typeProvider, |
| super( |
| library.libraryElement, source, typeProvider, library.errorListener, |
| nameScope: library.libraryScope, |
| inheritanceManager: library.inheritanceManager, |
| typeAnalyzerFactory: RestrictedStaticTypeAnalyzer.constructor); |
| |
| reanalyzeInitializer(VariableDeclaration variable) { |
| try { |
| _revisiting = true; |
| _nodeWasSkipped = false; |
| var node = variable.parent.parent; |
| var oldState; |
| var state = _stateAtDeclaration[node]; |
| if (state != null) { |
| oldState = new _ResolverState(this); |
| state.restore(this); |
| if (node is FieldDeclaration) { |
| var cls = node.parent as ClassDeclaration; |
| enclosingClass = cls.element; |
| } |
| } |
| visitNode(variable.initializer); |
| if (!_nodeWasSkipped) _visitedInitializers.add(variable); |
| if (oldState != null) oldState.restore(this); |
| } finally { |
| _revisiting = false; |
| } |
| } |
| |
| @override |
| Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| _stateAtDeclaration[node] = new _ResolverState(this); |
| return super.visitTopLevelVariableDeclaration(node); |
| } |
| |
| @override |
| Object visitFieldDeclaration(FieldDeclaration node) { |
| _stateAtDeclaration[node] = new _ResolverState(this); |
| return super.visitFieldDeclaration(node); |
| } |
| |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| var state = new _ResolverState(this); |
| try { |
| if (_revisiting) { |
| _stateAtDeclaration[node].restore(this); |
| } else { |
| _stateAtDeclaration[node] = state; |
| } |
| return super.visitVariableDeclaration(node); |
| } finally { |
| state.restore(this); |
| } |
| } |
| |
| @override |
| Object visitNode(AstNode node) { |
| if (skipMethodBodies && node is FunctionBody) { |
| _nodeWasSkipped = true; |
| return null; |
| } |
| if (_visitedInitializers.contains(node)) return null; |
| assert(node is! Statement || !skipMethodBodies); |
| return super.visitNode(node); |
| } |
| |
| @override |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| if (skipMethodBodies) { |
| node.accept(elementResolver); |
| node.accept(typeAnalyzer); |
| return null; |
| } else { |
| return super.visitMethodDeclaration(node); |
| } |
| } |
| |
| @override |
| Object visitFunctionDeclaration(FunctionDeclaration node) { |
| if (skipMethodBodies) { |
| node.accept(elementResolver); |
| node.accept(typeAnalyzer); |
| return null; |
| } else { |
| return super.visitFunctionDeclaration(node); |
| } |
| } |
| |
| @override |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| if (skipMethodBodies) { |
| node.accept(elementResolver); |
| node.accept(typeAnalyzer); |
| return null; |
| } else { |
| return super.visitConstructorDeclaration(node); |
| } |
| } |
| |
| @override |
| visitFieldFormalParameter(FieldFormalParameter node) { |
| // Ensure the field formal parameter's type is updated after inference. |
| // Normally this happens during TypeResolver, but that's before we've done |
| // inference on the field type. |
| var element = node.element; |
| if (element is FieldFormalParameterElement) { |
| if (element.type.isDynamic) { |
| // In malformed code, there may be no actual field. |
| if (element.field != null) { |
| element.type = element.field.type; |
| } |
| } |
| } |
| super.visitFieldFormalParameter(node); |
| } |
| } |
| |
| /// Internal state of the resolver, stored so we can reanalyze portions of the |
| /// AST quickly, without recomputing everything from the top. |
| class _ResolverState { |
| final TypePromotionManager_TypePromoteScope promotionScope; |
| final TypeOverrideManager_TypeOverrideScope overrideScope; |
| final Scope nameScope; |
| |
| _ResolverState(ResolverVisitor visitor) |
| : promotionScope = visitor.promoteManager.currentScope, |
| overrideScope = visitor.overrideManager.currentScope, |
| nameScope = visitor.nameScope; |
| |
| void restore(ResolverVisitor visitor) { |
| visitor.promoteManager.currentScope = promotionScope; |
| visitor.overrideManager.currentScope = overrideScope; |
| visitor.nameScope = nameScope; |
| } |
| } |
| |
| /// Overrides the default [StaticTypeAnalyzer] to adjust rules that are stricter |
| /// in the restricted type system and to infer types for untyped local |
| /// variables. |
| class RestrictedStaticTypeAnalyzer extends StaticTypeAnalyzer { |
| final TypeProvider _typeProvider; |
| Map<String, DartType> _objectMembers; |
| |
| RestrictedStaticTypeAnalyzer(ResolverVisitor r) |
| : _typeProvider = r.typeProvider, |
| super(r) { |
| _objectMembers = getObjectMemberMap(_typeProvider); |
| } |
| |
| static constructor(ResolverVisitor r) => new RestrictedStaticTypeAnalyzer(r); |
| |
| @override // to infer type from initializers |
| visitVariableDeclaration(VariableDeclaration node) { |
| _inferType(node); |
| return super.visitVariableDeclaration(node); |
| } |
| |
| /// Infer the type of a variable based on the initializer's type. |
| void _inferType(VariableDeclaration node) { |
| var initializer = node.initializer; |
| if (initializer == null) return; |
| |
| var declaredType = (node.parent as VariableDeclarationList).type; |
| if (declaredType != null) return; |
| var element = node.element; |
| if (element is! LocalVariableElement) return; |
| if (element.type != _typeProvider.dynamicType) return; |
| |
| var type = initializer.staticType; |
| if (type == null || type == _typeProvider.bottomType) return; |
| element.type = type; |
| if (element is PropertyInducingElement) { |
| element.getter.returnType = type; |
| if (!element.isFinal && !element.isConst) { |
| element.setter.parameters[0].type = type; |
| } |
| } |
| } |
| |
| // TODO(vsm): Use leafp's matchType here? |
| DartType _findIteratedType(InterfaceType type) { |
| if (type.element == _typeProvider.iterableType.element) { |
| var typeArguments = type.typeArguments; |
| assert(typeArguments.length == 1); |
| return typeArguments[0]; |
| } |
| |
| if (type == _typeProvider.objectType) return null; |
| |
| var result = _findIteratedType(type.superclass); |
| if (result != null) return result; |
| |
| for (final parent in type.interfaces) { |
| result = _findIteratedType(parent); |
| if (result != null) return result; |
| } |
| |
| for (final parent in type.mixins) { |
| result = _findIteratedType(parent); |
| if (result != null) return result; |
| } |
| |
| return null; |
| } |
| |
| @override |
| visitDeclaredIdentifier(DeclaredIdentifier node) { |
| super.visitDeclaredIdentifier(node); |
| if (node.type != null) return; |
| |
| var parent = node.parent as ForEachStatement; |
| var expr = parent.iterable; |
| var element = node.element as LocalVariableElementImpl; |
| var exprType = expr.staticType; |
| if (exprType is InterfaceType) { |
| var iteratedType = _findIteratedType(exprType); |
| if (iteratedType != null) { |
| element.type = iteratedType; |
| } |
| } |
| } |
| |
| bool _isSealed(DartType t) { |
| return _typeProvider.nonSubtypableTypes.contains(t); |
| } |
| |
| List<List> _genericList = null; |
| |
| DartType _matchGeneric(MethodInvocation node, Element element) { |
| var e = node.methodName.staticElement; |
| |
| if (_genericList == null) { |
| var minmax = (DartType tx, DartType ty) => (tx == ty && |
| (tx == _typeProvider.intType || tx == _typeProvider.doubleType)) |
| ? tx |
| : null; |
| |
| var map = (DartType tx) => (tx is FunctionType) |
| ? _typeProvider.iterableType.substitute4([tx.returnType]) |
| : null; |
| |
| // TODO(vsm): LUB? |
| var fold = (DartType tx, DartType ty) => |
| (ty is FunctionType && tx == ty.returnType) ? tx : null; |
| |
| // TODO(vsm): Flatten? |
| var then = (DartType tx) => (tx is FunctionType) |
| ? _typeProvider.futureType.substitute4([tx.returnType]) |
| : null; |
| |
| var wait = (DartType tx) { |
| // Iterable<Future<T>> -> Future<List<T>> |
| var futureType = _findIteratedType(tx); |
| if (futureType.element.type != _typeProvider.futureType) return null; |
| var typeArguments = futureType.typeArguments; |
| if (typeArguments.length != 1) return null; |
| var baseType = typeArguments[0]; |
| if (baseType.isDynamic) return null; |
| return _typeProvider.futureType.substitute4([ |
| _typeProvider.listType.substitute4([baseType]) |
| ]); |
| }; |
| |
| _genericList = [ |
| // Top-level methods |
| ['dart:math', 'max', 2, minmax], |
| ['dart:math', 'min', 2, minmax], |
| // Static methods |
| [_typeProvider.futureType, 'wait', 1, wait], |
| // Instance methods |
| [_typeProvider.iterableDynamicType, 'map', 1, map], |
| [_typeProvider.iterableDynamicType, 'fold', 2, fold], |
| [_typeProvider.futureDynamicType, 'then', 1, then], |
| ]; |
| } |
| |
| var targetType = node.target?.staticType; |
| var arguments = node.argumentList.arguments; |
| |
| for (var generic in _genericList) { |
| if (e?.name == generic[1]) { |
| if ((generic[0] is String && |
| element?.library.source.uri.toString() == generic[0]) || |
| (generic[0] is DartType && |
| targetType != null && |
| targetType.isSubtypeOf(generic[0]))) { |
| if (arguments.length == generic[2]) { |
| return Function.apply( |
| generic[3], arguments.map((arg) => arg.staticType).toList()); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @override // to propagate types to identifiers |
| visitMethodInvocation(MethodInvocation node) { |
| // TODO(jmesserly): we rely on having a staticType propagated to the |
| // methodName identifier. This shouldn't be necessary for method calls, so |
| // analyzer doesn't do it by default. Conceptually what we're doing here |
| // is asking for a tear off. We need this until we can fix #132, and rely |
| // on `node.staticElement == null` instead of `rules.isDynamicCall(node)`. |
| visitSimpleIdentifier(node.methodName); |
| |
| super.visitMethodInvocation(node); |
| |
| // Search for Object methods. |
| var name = node.methodName.name; |
| if (node.staticType.isDynamic && |
| _objectMembers.containsKey(name) && |
| isDynamicTarget(node.target)) { |
| var type = _objectMembers[name]; |
| if (type is FunctionType && |
| type.parameters.isEmpty && |
| node.argumentList.arguments.isEmpty) { |
| node.methodName.staticType = type; |
| // Only infer the type of the overall expression if we have an exact |
| // type - e.g., a sealed type. Otherwise, it may be too strict. |
| if (_isSealed(type.returnType)) { |
| node.staticType = type.returnType; |
| } |
| } |
| } |
| |
| var e = node.methodName.staticElement; |
| if (isInlineJS(e)) { |
| // Fix types for JS builtin calls. |
| // |
| // This code was taken from analyzer. It's not super sophisticated: |
| // only looks for the type name in dart:core, so we just copy it here. |
| // |
| // TODO(jmesserly): we'll likely need something that can handle a wider |
| // variety of types, especially when we get to JS interop. |
| var args = node.argumentList.arguments; |
| var first = args.isNotEmpty ? args.first : null; |
| if (first is SimpleStringLiteral) { |
| var typeStr = first.stringValue; |
| if (typeStr == '-dynamic') { |
| node.staticType = _typeProvider.bottomType; |
| } else { |
| var coreLib = _typeProvider.objectType.element.library; |
| var classElem = coreLib.getType(typeStr); |
| if (classElem != null) { |
| var type = fillDynamicTypeArgs(classElem.type, _typeProvider); |
| node.staticType = type; |
| } |
| } |
| } |
| } |
| |
| // Pretend dart:math's min and max are generic: |
| // |
| // T min<T extends num>(T x, T y); |
| // |
| // and infer T. In practice, this just means if the type of x and y are |
| // both double or both int, we treat that as the return type. |
| // |
| // The Dart spec has similar treatment for binary operations on numbers. |
| // |
| // TODO(jmesserly): remove this when we have a fix for |
| // https://github.com/dart-lang/dev_compiler/issues/28 |
| var inferred = _matchGeneric(node, e); |
| // TODO(vsm): If the inferred type is not a subtype, should we use a GLB instead? |
| if (inferred != null && inferred.isSubtypeOf(node.staticType)) { |
| node.staticType = inferred; |
| } |
| } |
| |
| void _inferObjectAccess( |
| Expression node, Expression target, SimpleIdentifier id) { |
| // Search for Object accesses. |
| var name = id.name; |
| if (node.staticType.isDynamic && |
| _objectMembers.containsKey(name) && |
| isDynamicTarget(target)) { |
| var type = _objectMembers[name]; |
| id.staticType = type; |
| // Only infer the type of the overall expression if we have an exact |
| // type - e.g., a sealed type. Otherwise, it may be too strict. |
| if (_isSealed(type)) { |
| node.staticType = type; |
| } |
| } |
| } |
| |
| @override |
| visitPropertyAccess(PropertyAccess node) { |
| super.visitPropertyAccess(node); |
| |
| _inferObjectAccess(node, node.target, node.propertyName); |
| } |
| |
| @override |
| visitPrefixedIdentifier(PrefixedIdentifier node) { |
| super.visitPrefixedIdentifier(node); |
| |
| _inferObjectAccess(node, node.prefix, node.identifier); |
| } |
| |
| @override |
| visitConditionalExpression(ConditionalExpression node) { |
| // TODO(vsm): The static type of a conditional should be the LUB of the |
| // then and else expressions. The analyzer appears to compute dynamic when |
| // one or the other is the null literal. Remove this fix once the |
| // corresponding analyzer bug is fixed: |
| // https://code.google.com/p/dart/issues/detail?id=22854 |
| super.visitConditionalExpression(node); |
| if (node.staticType.isDynamic) { |
| var thenExpr = node.thenExpression; |
| var elseExpr = node.elseExpression; |
| if (thenExpr.staticType.isBottom) { |
| node.staticType = elseExpr.staticType; |
| } else if (elseExpr.staticType.isBottom) { |
| node.staticType = thenExpr.staticType; |
| } |
| } |
| } |
| |
| // Review note: no longer need to override visitFunctionExpression, this is |
| // handled by the analyzer internally. |
| // TODO(vsm): in visitbinaryExpression: check computeStaticReturnType result? |
| // TODO(vsm): in visitFunctionDeclaration: Should we ever use the expression |
| // type in a (...) => expr or just the written type? |
| |
| } |