Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:collection' show HashMap, HashSet, Queue; | |
| 6 | |
| 7 import 'package:analyzer/dart/ast/ast.dart' show Identifier; | |
| 8 import 'package:analyzer/dart/element/element.dart'; | |
| 9 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; | |
| 10 | |
| 11 import '../js_ast/js_ast.dart' as JS; | |
| 12 import 'element_helpers.dart'; | |
| 13 import 'js_names.dart' as JS; | |
| 14 | |
| 15 /// Dart allows all fields to be overridden. | |
| 16 /// | |
| 17 /// To prevent a performance/code size penalty for allowing this, we analyze | |
| 18 /// private classes within each library that is being compiled to determine | |
| 19 /// if those fields should be virtual or not. In effect, we devirtualized fields | |
|
Leaf
2017/03/27 23:26:19
devirtualize
| |
| 20 /// when possible by analyzing the class heirarchy and using knowledge of | |
|
vsm
2017/03/27 22:31:27
heirarchy -> hierarchy
Jennifer Messerly
2017/03/27 23:16:28
Done.
| |
| 21 /// which members are private and thus, could not be overridden outside of the | |
| 22 /// current library. | |
| 23 class VirtualFieldModel { | |
| 24 final _modelForLibrary = | |
| 25 new HashMap<LibraryElement, _LibraryVirtualFieldModel>(); | |
| 26 | |
| 27 _LibraryVirtualFieldModel _getModel(LibraryElement library) => | |
| 28 _modelForLibrary.putIfAbsent( | |
| 29 library, () => new _LibraryVirtualFieldModel.build(library)); | |
| 30 | |
| 31 /// Returns true if a field is virtual. | |
| 32 bool isVirtual(FieldElement field) => | |
| 33 _getModel(field.library).isVirtual(field); | |
| 34 } | |
| 35 | |
| 36 /// This is a building block of [VirtualFieldModel], used to track information | |
| 37 /// about a single library that has been analyzed. | |
| 38 class _LibraryVirtualFieldModel { | |
| 39 /// Fields that are private (or public fields of a private class) and | |
| 40 /// overridden in this library. | |
| 41 /// | |
| 42 /// This means we must generate them as virtual fields using a property pair | |
| 43 /// in JavaScript. | |
| 44 final _overriddenPrivateFields = new HashSet<FieldElement>(); | |
| 45 | |
| 46 /// Private classes that can be extended outside of this library. | |
| 47 /// | |
| 48 /// Normally private classes cannot be accessed outside this library, however, | |
| 49 /// this can happen if they are extended by a public class, for example: | |
| 50 /// | |
| 51 /// class _A { int x = 42; } | |
| 52 /// class _B { int x = 42; } | |
| 53 /// | |
| 54 /// // _A is now effectively public for the purpose of overrides. | |
| 55 /// class C extends _A {} | |
| 56 /// | |
| 57 /// The class _A must treat is "x" as virtual, however _B does not. | |
| 58 final _extensiblePrivateClasses = new HashSet<ClassElement>(); | |
| 59 | |
| 60 _LibraryVirtualFieldModel.build(LibraryElement library) { | |
| 61 var allTypes = library.units.expand((u) => u.types).toList(); | |
| 62 | |
| 63 // The set of public types is our initial extensible type set. | |
| 64 // From there, visit all immediate private types in this library, and so on | |
| 65 // from those private types, marking them as extensible. | |
| 66 var typesToVisit = | |
| 67 new Queue<ClassElement>.from(allTypes.where((t) => t.isPublic)); | |
| 68 while (typesToVisit.isNotEmpty) { | |
| 69 var extensibleType = typesToVisit.removeFirst(); | |
| 70 | |
| 71 // For each supertype of a public type in this library, | |
| 72 // if we encounter a private class, we mark it as being extended, and | |
| 73 // add it to our work set if this is the first time we've visited it. | |
| 74 for (var type in getImmediateSuperclasses(extensibleType)) { | |
| 75 if (type.isPublic && type.library == library) { | |
|
Jennifer Messerly
2017/03/27 22:05:58
this should be "!type.isPublic". Fix on the way. I
| |
| 76 if (_extensiblePrivateClasses.add(type)) typesToVisit.add(type); | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 // ClassElement can only look up inherited members with an O(N) scan through | |
| 82 // the class, so we build up a mapping of all fields in the library ahead of | |
| 83 // time. | |
| 84 var allFields = | |
| 85 new HashMap<ClassElement, HashMap<String, FieldElement>>.fromIterable( | |
| 86 allTypes, | |
| 87 value: (t) => new HashMap.fromIterable( | |
| 88 t.fields.where((f) => !f.isStatic), | |
| 89 key: (f) => f.name)); | |
| 90 | |
| 91 for (var type in allTypes) { | |
| 92 // Collect all inherited fields from superclasses in this library. | |
| 93 var inheritedFields = new HashMap<String, List<FieldElement>>(); | |
| 94 for (var supertype in getSuperclassesInSameLibrary(type)) { | |
| 95 allFields[supertype].forEach((name, field) { | |
| 96 inheritedFields.putIfAbsent(name, () => []).add(field); | |
| 97 }); | |
| 98 } | |
| 99 | |
| 100 // Visit accessors in the current class, and see if they override an | |
| 101 // otherwise private field. | |
| 102 for (var accessor in type.accessors) { | |
| 103 // For getter/setter pairs only process them once. | |
| 104 if (accessor.correspondingGetter != null) continue; | |
| 105 // Ignore abstract or static accessors. | |
| 106 if (accessor.isAbstract || accessor.isStatic) continue; | |
| 107 // Ignore public accessors in extensible classes. | |
| 108 if (accessor.isPublic && | |
| 109 (type.isPublic || _extensiblePrivateClasses.contains(type))) { | |
| 110 continue; | |
| 111 } | |
| 112 // Look in the super class to see if we're overriding a field in our | |
| 113 // library, if so mark that field and this one as | |
| 114 var inherited = inheritedFields[accessor.variable.name]; | |
| 115 if (inherited != null) _overriddenPrivateFields.addAll(inherited); | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 /// Returns true if a field inside this library is virtual. | |
| 121 bool isVirtual(FieldElement field) { | |
| 122 // If the field was marked non-virtual, we know for sure. | |
| 123 if (!field.isVirtual) return false; | |
| 124 if (field.isStatic) return false; | |
| 125 | |
| 126 var type = field.enclosingElement; | |
| 127 var library = type.library; | |
| 128 if (library.isInSdk && library.source.uri.toString().startsWith('dart:_')) { | |
| 129 // There should be no extensible fields in private SDK libraries. | |
| 130 return false; | |
| 131 } | |
| 132 | |
| 133 if (field.isPublic) { | |
| 134 // Public fields in public classes (or extensible private classes) | |
| 135 // are always virtual. | |
| 136 // They could be overridden by someone using our library. | |
| 137 if (type.isPublic) return true; | |
| 138 if (_extensiblePrivateClasses.contains(type)) return true; | |
| 139 } | |
| 140 | |
| 141 // Otherwise, the field is effectively private and we only need to make it | |
| 142 // virtual if it's overridden. | |
| 143 return _overriddenPrivateFields.contains(field); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 /// Tracks how fields, getters and setters are represented when emitting JS. | |
| 148 /// | |
| 149 /// Dart classes have implicit features that must be made explicit: | |
| 150 /// | |
| 151 /// - virtual fields induce a getter and setter pair. | |
| 152 /// - getters and setters are independent. | |
| 153 /// - getters and setters can be overridden. | |
| 154 /// | |
| 155 class ClassPropertyModel { | |
|
Jennifer Messerly
2017/03/27 21:59:52
this is unchanged from lib/src/compiler/class_prop
| |
| 156 /// Fields that are virtual, that is, they must be generated as a property | |
| 157 /// pair in JavaScript. | |
| 158 /// | |
| 159 /// The value property stores the symbol used for the field's storage slot. | |
| 160 final virtualFields = <FieldElement, JS.TemporaryId>{}; | |
| 161 | |
| 162 /// Static fields that are overridden, this does not matter for Dart but in | |
| 163 /// JS we need to take care initializing these because JS classes inherit | |
| 164 /// statics. | |
| 165 final staticFieldOverrides = new HashSet<FieldElement>(); | |
| 166 | |
| 167 /// The set of inherited getters, used because JS getters/setters are paired, | |
| 168 /// so if we're generating a setter we may need to emit a getter that calls | |
| 169 /// super. | |
| 170 final inheritedGetters = new HashSet<String>(); | |
| 171 | |
| 172 /// The set of inherited setters, used because JS getters/setters are paired, | |
| 173 /// so if we're generating a getter we may need to emit a setter that calls | |
| 174 /// super. | |
| 175 final inheritedSetters = new HashSet<String>(); | |
| 176 | |
| 177 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem, | |
| 178 Iterable<ExecutableElement> extensionMembers) { | |
| 179 // Visit superclasses to collect information about their fields/accessors. | |
| 180 // This is expensive so we try to collect everything in one pass. | |
| 181 for (var base in getSuperclasses(classElem)) { | |
| 182 for (var accessor in base.accessors) { | |
| 183 // For getter/setter pairs only process them once. | |
| 184 if (accessor.correspondingGetter != null) continue; | |
| 185 | |
| 186 var field = accessor.variable; | |
| 187 var name = field.name; | |
| 188 // Ignore private names from other libraries. | |
| 189 if (Identifier.isPrivateName(name) && | |
| 190 accessor.library != classElem.library) { | |
| 191 continue; | |
| 192 } | |
| 193 | |
| 194 if (field.getter?.isAbstract == false) inheritedGetters.add(name); | |
| 195 if (field.setter?.isAbstract == false) inheritedSetters.add(name); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 var extensionNames = | |
| 200 new HashSet<String>.from(extensionMembers.map((e) => e.name)); | |
| 201 | |
| 202 // Visit accessors in the current class, and see if they need to be | |
| 203 // generated differently based on the inherited fields/accessors. | |
| 204 for (var accessor in classElem.accessors) { | |
| 205 // For getter/setter pairs only process them once. | |
| 206 if (accessor.correspondingGetter != null) continue; | |
| 207 // Also ignore abstract fields. | |
| 208 if (accessor.isAbstract) continue; | |
| 209 | |
| 210 var field = accessor.variable; | |
| 211 var name = field.name; | |
| 212 // Is it a field? | |
| 213 if (!field.isSynthetic && field is FieldElementImpl) { | |
| 214 if (inheritedGetters.contains(name) || | |
| 215 inheritedSetters.contains(name) || | |
| 216 extensionNames.contains(name) || | |
| 217 fieldModel.isVirtual(field)) { | |
|
Jennifer Messerly
2017/03/27 21:59:52
except for this 1 line
| |
| 218 if (field.isStatic) { | |
| 219 staticFieldOverrides.add(field); | |
| 220 } else { | |
| 221 virtualFields[field] = new JS.TemporaryId(name); | |
| 222 } | |
| 223 } | |
| 224 } | |
| 225 } | |
| 226 } | |
| 227 } | |
| OLD | NEW |