| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:collection'; | |
| import 'package:collection/collection.dart'; | |
| import 'package:kernel/core_types.dart'; | |
| import 'package:kernel/kernel.dart' hide Pattern; | |
| import 'package:path/path.dart' as p; | |
| import 'constants.dart'; | |
| Never throwUnsupportedInvalidType(InvalidType type) => throw UnsupportedError( | |
| 'Unsupported invalid type $type (${type.runtimeType}).', | |
| ); | |
| Never throwUnsupportedAuxiliaryType(AuxiliaryType type) => | |
| throw UnsupportedError( | |
| 'Unsupported auxiliary type $type (${type.runtimeType}).', | |
| ); | |
| Never throwUnsupportedExperimentalType(ExperimentalType type) => | |
| throw UnsupportedError( | |
| 'Unsupported experimental type $type (${type.runtimeType}).', | |
| ); | |
| Constructor? unnamedConstructor(Class c) => | |
| c.constructors.firstWhereOrNull((c) => c.name.text == ''); | |
| /// Returns `true` when a class is eligible to be used as a mixin without | |
| /// having the mixin class modifier in Dart language versions < 3.0. | |
| extension ClassExtension on Class { | |
| bool isLegacyMixinEligible(CoreTypes coreTypes) => | |
| !isMixinDeclaration && superclass == coreTypes.objectClass; | |
| } | |
| /// Returns the enclosing library for reference [node]. | |
| Library getLibrary(NamedNode node) { | |
| for (TreeNode? n = node; n != null; n = n.parent) { | |
| if (n is Library) return n; | |
| } | |
| throw UnsupportedError('Could not find a containing library for $node'); | |
| } | |
| final Pattern _syntheticTypeCharacters = RegExp('[&^#.|]'); | |
| String escapeIdentifier(String identifier) { | |
| // Remove the special characters used to encode mixin application class names | |
| // and extension method / parameter names which are legal in Kernel, but not | |
| // in JavaScript. | |
| // | |
| // Note, there is an implicit assumption here that we won't have | |
| // collisions since everything is mapped to \$. That may work out fine given | |
| // how these are synthesized, but may need to revisit. | |
| return identifier.replaceAll(_syntheticTypeCharacters, r'$'); | |
| } | |
| /// Returns the escaped name for class [node]. | |
| /// | |
| /// The caller of this function has to make sure that this name is unique in | |
| /// the current scope. | |
| String getLocalClassName(Class node) => escapeIdentifier(node.name); | |
| /// Returns the escaped name for the type parameter [node]. | |
| String getTypeParameterName( | |
| /* TypeParameter | StructuralParameter */ Object node, | |
| ) { | |
| assert(node is TypeParameter || node is StructuralParameter); | |
| if (node is TypeParameter) { | |
| return escapeIdentifier(node.name!); | |
| } else { | |
| node as StructuralParameter; | |
| return escapeIdentifier(node.name!); | |
| } | |
| } | |
| String getTopLevelName(NamedNode n) { | |
| if (n is Procedure) return n.name.text; | |
| if (n is Class) return n.name; | |
| if (n is Typedef) return n.name; | |
| if (n is Field) return n.name.text; | |
| return n.reference.canonicalName!.name; | |
| } | |
| /// Given an annotated [node] and a [test] function, returns the first matching | |
| /// constant valued annotation. | |
| /// | |
| /// For example if we had the ClassDeclaration node for `FontElement`: | |
| /// | |
| /// ``` | |
| /// @js.JS('HTMLFontElement') | |
| /// @deprecated | |
| /// class FontElement { ... } | |
| /// ``` | |
| /// | |
| /// We could match `@deprecated` with a test function like: | |
| /// | |
| /// ``` | |
| /// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore | |
| /// ``` | |
| Expression? findAnnotation(TreeNode node, bool Function(Expression) test) { | |
| List<Expression> annotations; | |
| if (node is Class) { | |
| annotations = node.annotations; | |
| } else if (node is Typedef) { | |
| annotations = node.annotations; | |
| } else if (node is Procedure) { | |
| annotations = node.annotations; | |
| } else if (node is Member) { | |
| annotations = node.annotations; | |
| } else if (node is Library) { | |
| annotations = node.annotations; | |
| } else { | |
| return null; | |
| } | |
| return annotations.firstWhereOrNull(test); | |
| } | |
| /// Returns true if [value] represents an annotation for class [className] in | |
| /// "dart:" library [libraryName]. | |
| bool isBuiltinAnnotation( | |
| Expression value, | |
| String libraryName, | |
| String className, | |
| ) { | |
| var c = getAnnotationClass(value); | |
| if (c != null && c.name == className) { | |
| var uri = c.enclosingLibrary.importUri; | |
| return uri.isScheme('dart') && uri.path == libraryName; | |
| } | |
| return false; | |
| } | |
| /// Gets the class of the instance referred to by metadata annotation [node]. | |
| /// | |
| /// For example: | |
| /// | |
| /// - `@JS()` would return the "JS" class in "dart:_js_annotations". | |
| /// - `@anonymous` would return the "_Anonymous" class in | |
| /// "dart:_js_annotations". | |
| /// | |
| /// This function works regardless of whether the CFE is evaluating constants, | |
| /// or whether the constant is a field reference (such as "anonymous" above). | |
| Class? getAnnotationClass(Expression node) { | |
| if (node is ConstantExpression) { | |
| var constant = node.constant; | |
| if (constant is InstanceConstant) return constant.classNode; | |
| } else if (node is ConstructorInvocation) { | |
| return node.target.enclosingClass; | |
| } else if (node is StaticGet) { | |
| var type = node.target.getterType; | |
| if (type is InterfaceType) return type.classNode; | |
| } | |
| return null; | |
| } | |
| bool isFromEnvironmentInvocation(CoreTypes coreTypes, StaticInvocation node) { | |
| var target = node.target; | |
| return node.isConst && | |
| target.name.text == 'fromEnvironment' && | |
| target.enclosingLibrary == coreTypes.coreLibrary; | |
| } | |
| List<Class> getSuperclasses(Class? c) { | |
| var result = <Class>[]; | |
| var visited = HashSet<Class>(); | |
| while (c != null && visited.add(c)) { | |
| for (var m = c.mixedInClass; m != null; m = m.mixedInClass) { | |
| result.add(m); | |
| } | |
| var superclass = c.superclass; | |
| if (superclass == null) break; | |
| result.add(superclass); | |
| c = superclass; | |
| } | |
| return result; | |
| } | |
| List<Class> getImmediateSuperclasses(Class c) { | |
| var result = <Class>[]; | |
| var m = c.mixedInClass; | |
| if (m != null) result.add(m); | |
| var s = c.superclass; | |
| if (s != null) result.add(s); | |
| return result; | |
| } | |
| Expression? getInvocationReceiver(InvocationExpression node) { | |
| if (node is InstanceInvocation) { | |
| return node.receiver; | |
| } else if (node is DynamicInvocation) { | |
| return node.receiver; | |
| } else if (node is FunctionInvocation) { | |
| return node.receiver; | |
| } else if (node is LocalFunctionInvocation) { | |
| return VariableGet(node.variable); | |
| } | |
| return null; | |
| } | |
| bool isInlineJS(Member e) => | |
| e is Procedure && _isProcedureFromForeignHelper('JS', e); | |
| /// Returns `true` if [p] is the procedure named [name] from the | |
| /// 'dart:_foreign_helper' library. | |
| bool _isProcedureFromForeignHelper(String name, Procedure p) => | |
| p.name.text == name && | |
| p.enclosingLibrary.importUri.toString() == 'dart:_foreign_helper'; | |
| /// Whether the parameter [p] is covariant (either explicitly `covariant` or | |
| /// implicitly due to generics) and needs a check for soundness. | |
| bool isCovariantParameter(FunctionParameter p) { | |
| return p.isCovariantByDeclaration || p.isCovariantByClass; | |
| } | |
| /// Whether the field [p] is covariant (either explicitly `covariant` or | |
| /// implicitly due to generics) and needs a check for soundness. | |
| bool isCovariantField(Field f) { | |
| return f.isCovariantByDeclaration || f.isCovariantByClass; | |
| } | |
| /// Returns true iff this factory constructor just throws [UnsupportedError]/ | |
| /// | |
| /// `dart:html` has many of these. | |
| bool isUnsupportedFactoryConstructor(Procedure node) { | |
| if (node.name.isPrivate && node.enclosingLibrary.importUri.isScheme('dart')) { | |
| var body = node.function.body; | |
| if (body is Block) { | |
| var statements = body.statements; | |
| if (statements.length == 1) { | |
| var statement = statements[0]; | |
| if (statement is ExpressionStatement) { | |
| var expr = statement.expression; | |
| if (expr is Throw) { | |
| var error = expr.expression; | |
| // HTML adds a lot of private constructors that are unreachable. | |
| // Skip these. | |
| return isBuiltinAnnotation(error, 'core', 'UnsupportedError'); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool hasPragma(Annotatable node, String name, CoreTypes coreTypes) { | |
| for (var a in node.annotations) { | |
| if (a is! ConstantExpression) continue; | |
| final value = a.constant; | |
| if (value is! InstanceConstant) continue; | |
| if (value.classReference != coreTypes.pragmaClass.reference) { | |
| continue; | |
| } | |
| final nameValue = value.fieldValues[coreTypes.pragmaName.fieldReference]; | |
| if (nameValue is StringConstant && nameValue.value == name) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Gets the real supertype of [c] and the list of [mixins] in reverse | |
| /// application order (mixins will appear before ones they override). | |
| /// | |
| /// This is used to ignore synthetic mixin application classes. | |
| /// | |
| // TODO(jmesserly): consider replacing this with Kernel's mixin unrolling | |
| Class getSuperclassAndMixins(Class c, List<Class> mixins) { | |
| assert(mixins.isEmpty); | |
| assert(c.superclass != null); | |
| var mixedInClass = c.mixedInClass; | |
| if (mixedInClass != null) mixins.add(mixedInClass); | |
| var sc = c.superclass!; | |
| for (; sc.isAnonymousMixin; sc = sc.superclass!) { | |
| mixedInClass = sc.mixedInClass; | |
| if (mixedInClass != null) mixins.add(sc.mixedInClass!); | |
| } | |
| return sc; | |
| } | |
| /// Returns true if a switch statement contains any continues with a label. | |
| bool hasLabeledContinue(SwitchStatement node) { | |
| var visitor = LabelContinueFinder(); | |
| node.accept(visitor); | |
| return visitor.found; | |
| } | |
| class LabelContinueFinder extends RecursiveVisitor { | |
| var found = false; | |
| void visit(Statement? s) { | |
| if (!found && s != null) s.accept(this); | |
| } | |
| @override | |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) => | |
| found = true; | |
| } | |
| /// Returns true if a class contains any 'super' calls. | |
| bool hasSuper(Class node) { | |
| var visitor = SuperFinder(); | |
| node.accept(visitor); | |
| return visitor.found; | |
| } | |
| class SuperFinder extends RecursiveVisitor { | |
| var found = false; | |
| void visit(Statement? s) { | |
| if (!found && s != null) s.accept(this); | |
| } | |
| @override | |
| void visitAbstractSuperPropertyGet(AbstractSuperPropertyGet node) => | |
| found = true; | |
| @override | |
| void visitAbstractSuperPropertySet(AbstractSuperPropertySet node) => | |
| found = true; | |
| @override | |
| void visitSuperPropertyGet(SuperPropertyGet node) => found = true; | |
| @override | |
| void visitSuperPropertySet(SuperPropertySet node) => found = true; | |
| @override | |
| void visitAbstractSuperMethodInvocation(AbstractSuperMethodInvocation node) => | |
| found = true; | |
| @override | |
| void visitSuperMethodInvocation(SuperMethodInvocation node) => found = true; | |
| } | |
| /// Returns `true` if any of [n]s children are a [FunctionExpression] node. | |
| bool containsFunctionExpression(Node n) { | |
| var visitor = _FunctionExpressionFinder.instance; | |
| visitor.found = false; | |
| n.accept(visitor); | |
| return visitor.found; | |
| } | |
| class _FunctionExpressionFinder extends RecursiveVisitor { | |
| var found = false; | |
| static final instance = _FunctionExpressionFinder(); | |
| @override | |
| void visitFunctionExpression(FunctionExpression node) => found = true; | |
| } | |
| /// Whether [member] is declared native, as in: | |
| /// | |
| /// ``` | |
| /// void foo() native; | |
| /// ``` | |
| /// | |
| /// This syntax is only allowed in sdk libraries and native tests. | |
| bool isNative(Member member) => | |
| // The CFE represents `native` members with the `external` bit and with an | |
| // internal @ExternalName annotation as a marker. | |
| member.isExternal && member.annotations.any(_isNativeMarkerAnnotation); | |
| bool _isNativeMarkerAnnotation(Expression annotation) { | |
| if (annotation is ConstantExpression) { | |
| var constant = annotation.constant; | |
| if (constant is InstanceConstant && | |
| constant.classNode.name == 'ExternalName' && | |
| _isDartInternal(constant.classNode.enclosingLibrary.importUri)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool _isDartInternal(Uri uri) => | |
| uri.isScheme('dart') && uri.path == '_internal'; | |
| /// Collects all `TypeParameter`s from the `TypeParameterType`s present in the | |
| /// visited `DartType`. | |
| class TypeParameterFinder extends RecursiveVisitor { | |
| final _found = <TypeParameter>{}; | |
| static TypeParameterFinder? _instance; | |
| TypeParameterFinder._(); | |
| factory TypeParameterFinder.instance() { | |
| if (_instance != null) return _instance!; | |
| return TypeParameterFinder._(); | |
| } | |
| Set<TypeParameter> find(DartType type) { | |
| _found.clear(); | |
| type.accept(this); | |
| return _found; | |
| } | |
| @override | |
| void visitTypeParameterType(TypeParameterType node) => | |
| _found.add(node.parameter); | |
| } | |
| /// Collects [InterfaceType] nodes that appear in in a DartType. | |
| class InterfaceTypeExtractor extends RecursiveVisitor { | |
| final Set<InterfaceType> _found = {}; | |
| @override | |
| void visitInterfaceType(InterfaceType node) { | |
| _found.add(node); | |
| node.visitChildren(this); | |
| } | |
| /// Returns all [InterfaceType]s that appear in [type]. | |
| Iterable<InterfaceType> extract(DartType type) { | |
| type.accept(this); | |
| return _found; | |
| } | |
| } | |
| /// Replaces [VariableGet] nodes with a different expression defined by a | |
| /// replacement map. | |
| class VariableGetReplacer extends Transformer { | |
| final Map<Variable, Expression> _replacements; | |
| VariableGetReplacer(this._replacements); | |
| @override | |
| TreeNode visitVariableGet(VariableGet node) { | |
| var replacement = _replacements[node.variable]; | |
| return replacement ?? node; | |
| } | |
| } | |
| /// Tests a [StaticInvocation] node to determine if it would be safe to inline. | |
| /// | |
| /// Each static invocation should be inspected individually as this class only | |
| /// inspects the body of a given invocation target, and does not recurse | |
| /// transitively into the bodies of other invocations it finds. | |
| /// | |
| /// The determination of what is safe to inline is specifically targeting simple | |
| /// methods in the dart:_rti library and has not been validated to use as an | |
| /// inliner for static methods from any other libraries. Instead of inlining by | |
| /// introducing let variables that preserve the evaluation order of the call, | |
| /// only code where it is safe to replace the argument of the call at the | |
| /// place where it used is considered suitable. | |
| /// | |
| /// The body of the target method may only contain simple expressions known to | |
| /// have no side-effects like null, bool or string literals, constants, enum | |
| /// values, and variable accesses. Additionally some static invocations from the | |
| /// runtime libraries are permitted in the body when they have been manually | |
| /// inspected for side effects and annotated as safe with | |
| /// `@pragma('ddc:trust-inline`). After the last method argument has been used, | |
| /// this restriction is weakened to allow any static invocations to appear. | |
| class BasicInlineTester extends TreeVisitorDefault<bool> { | |
| /// The constants used to access constant evaluation for annotations. | |
| final DevCompilerConstants _constants; | |
| /// The order that the arguments are expected to used in the body of the | |
| /// function for it to be considered safe for inlining. | |
| /// | |
| /// This is essential to preserve the execution order of the expressions | |
| /// passed as arguments. | |
| List<Variable>? _expectedArgumentOrder; | |
| /// The position in [_expectedArgumentOrder] to begin looking for the next | |
| /// used argument. | |
| int _nextArgIndex = 0; | |
| /// Returns `true` if uses of all the expected arguments have already been | |
| /// seen by the visitor. | |
| bool get _allArgumentsUsed => _nextArgIndex >= _expectedArgumentOrder!.length; | |
| BasicInlineTester(this._constants); | |
| /// Returns `true` when [possibleInline] is considered simple enough to | |
| /// be safe to inline. | |
| /// | |
| /// The considerations for inlining are designed specifically to target select | |
| /// invocations in the dart:_rti library. | |
| bool canInline(FunctionNode possibleInline) { | |
| if (possibleInline.namedParameters.isNotEmpty || | |
| possibleInline.typeParameters.isNotEmpty || | |
| possibleInline.positionalParameters.length != | |
| possibleInline.requiredParameterCount) { | |
| // Only consider functions with required positional arguments. | |
| return false; | |
| } | |
| final body = possibleInline.body; | |
| // No code available here to inline. | |
| if (body == null) return false; | |
| _expectedArgumentOrder = possibleInline.positionalParameters; | |
| _nextArgIndex = 0; | |
| // Important to note that the static invocation being considered for inline | |
| // is not visited. Instead the body of the target function being invoked is | |
| // visited to determine if inlining is feasible. Any other static | |
| // invocations appearing within that body *will* be visited. | |
| final result = body.accept(this); | |
| // Avoid retaining any of the arguments after returning. | |
| _expectedArgumentOrder = null; | |
| return result; | |
| } | |
| /// Returns `true` when [node] is annotated with | |
| /// `@pragma('ddc:trust-inline`)`. | |
| bool _hasTrustInlinePragma(NamedNode node) { | |
| var annotation = findAnnotation(node, (e) { | |
| if (!isBuiltinAnnotation(e, 'core', 'pragma')) return false; | |
| var name = _constants.getFieldValueFromAnnotation(e, 'name') as String?; | |
| return name == 'ddc:trust-inline'; | |
| }); | |
| return annotation != null; | |
| } | |
| @override | |
| bool defaultTreeNode(Node _) => false; | |
| @override | |
| bool visitReturnStatement(ReturnStatement node) { | |
| var returnExpression = node.expression; | |
| return returnExpression == null ? false : returnExpression.accept(this); | |
| } | |
| @override | |
| bool visitBlock(Block node) { | |
| if (node.statements.length != 1) return false; | |
| return node.statements.single.accept(this); | |
| } | |
| @override | |
| bool visitStaticInvocation(StaticInvocation node) { | |
| // Reaching this visitor means that a static invocation appears in the body | |
| // of the function being evaluated for inlining. | |
| var positionalArgs = node.arguments.positional; | |
| if (isInlineJS(node.target)) { | |
| // Ensure JS expressions do not span multiple statements. Since these are | |
| // inlined elsewhere we don't want to accidentally combine multiple | |
| // Javascript statements into a position where they don't belong. Instead | |
| // of inspecting if it is valid, just reject any source templates that | |
| // contain ';' characters. | |
| var codeLiteral = positionalArgs[1]; | |
| String? code; | |
| if (codeLiteral is StringLiteral) { | |
| code = codeLiteral.value; | |
| } else if (codeLiteral is ConstantExpression) { | |
| var constant = codeLiteral.constant; | |
| if (constant is StringConstant) { | |
| code = constant.value; | |
| } | |
| } | |
| if (code == null || code.contains(';')) return false; | |
| } | |
| // Inspect the arguments. | |
| for (var argument in positionalArgs) { | |
| if (!argument.accept(this)) return false; | |
| } | |
| // While all of the uses of the arguments are still being discovered, | |
| // only allow additional static invocations explicitly annotated as | |
| // trusted. This prevents inlining a static invocation that could | |
| // change the evaluation order of the expressions passed as arguments. | |
| // Example: | |
| // ``` | |
| // echo(s) { | |
| // print(s); | |
| // return s; | |
| // } | |
| // | |
| // dangerous(arg1, arg2) { | |
| // return fn(arg1, echo('third'), arg2); | |
| // } | |
| // ``` | |
| // Calls to `dangerous()` should not be inlined without let variables | |
| // because the expression evaluation order would be disrupted: | |
| // ``` | |
| // dangerous(echo('first'), echo('second')); | |
| // | | | |
| // V V | |
| // fn(echo('first'), echo('third'), echo('second')) // INVALID! | |
| return _allArgumentsUsed || _hasTrustInlinePragma(node.target); | |
| } | |
| @override | |
| bool visitBoolLiteral(BoolLiteral _) => true; | |
| @override | |
| bool visitNullLiteral(NullLiteral _) => true; | |
| @override | |
| bool visitStringLiteral(StringLiteral _) => true; | |
| @override | |
| bool visitConstantExpression(ConstantExpression _) => true; | |
| @override | |
| bool visitVariableGet(VariableGet node) { | |
| // The variable is an argument of the function to be inlined. Verify it | |
| // appears in an order consistent with the order the arguments appeared | |
| // in the call. | |
| final location = _expectedArgumentOrder!.indexOf( | |
| node.variable, | |
| _nextArgIndex, | |
| ); | |
| // Either the variable isn't one of the expected arguments at all, or it is | |
| // appearing out of the expected order. | |
| if (location == -1) return false; | |
| // Advance the start of the expected range past the variable just found to | |
| // ensure the variable can only be used once. | |
| _nextArgIndex = location + 1; | |
| return true; | |
| } | |
| @override | |
| bool visitField(Field node) => node.isEnumElement; | |
| @override | |
| bool visitStaticGet(StaticGet node) { | |
| return node.target.accept(this); | |
| } | |
| } | |
| extension MemberHelpers on Member { | |
| /// Returns `true` if this is the `hashCode` or `runtimeType` getter. | |
| bool get isCoreObjectGetter => | |
| isInstanceMember && | |
| (name.text == 'hashCode' || name.text == 'runtimeType'); | |
| } | |
| extension ProcedureHelpers on Procedure { | |
| /// Returns `true` if [name] is an operator method that is available on | |
| /// primitive types (`int`, `double`, `num`, `String`, `bool`). | |
| /// | |
| /// This does not include logical operators that cannot be user-defined | |
| /// (`!`, `&&` and `||`). | |
| bool get isPrimitiveOperator { | |
| switch (name.text) { | |
| case '==': | |
| case '~': | |
| case '^': | |
| case '|': | |
| case '&': | |
| case '>>': | |
| case '<<': | |
| case '>>>': | |
| case '+': | |
| case 'unary-': | |
| case '-': | |
| case '*': | |
| case '/': | |
| case '~/': | |
| case '>': | |
| case '<': | |
| case '>=': | |
| case '<=': | |
| case '%': | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Returns `true` if this is any `toString` or `noSuchMethod` method | |
| /// implementation. | |
| /// | |
| /// See [isToStringOrNoSuchMethodWithDefaultSignature] to test for a | |
| /// `toString` or `noSuchMethod` implementation with a default signature. | |
| bool get isToStringOrNoSuchMethod => | |
| isInstanceMember && | |
| (name.text == 'toString' || name.text == 'noSuchMethod'); | |
| /// Returns `true` if this is any `toString` or `noSuchMethod` method | |
| /// implementation with the default signature. | |
| /// | |
| /// For example this will return `false` if this is an override with optional | |
| /// positional or named parameters. | |
| /// | |
| /// See [isToStringOrNoSuchMethod] to test for any `toString` or | |
| /// `noSuchMethod` implementation. | |
| bool get isToStringOrNoSuchMethodWithDefaultSignature { | |
| if (function.namedParameters.isNotEmpty || | |
| function.typeParameters.isNotEmpty) { | |
| return false; | |
| } | |
| if (name.text == 'toString') { | |
| return function.positionalParameters.isEmpty; | |
| } else if (name.text == 'noSuchMethod') { | |
| return function.positionalParameters.length == 1; | |
| } | |
| return false; | |
| } | |
| } | |
| extension InstanceInvocationHelpers on InstanceInvocation { | |
| /// Returns `true` when this represents an invocation of `List.add()` that | |
| /// can be optimized. | |
| /// | |
| /// For example, a List literal that uses control flow collections to add | |
| /// elements can safely push directly to the backing array: | |
| /// | |
| /// ``` | |
| /// final myList = [if (test) 1, ...otherList, 2, 3, if (otherTest) 1]; | |
| /// ``` | |
| /// | |
| /// The optimized add operation can skip checks for a growable or modifiable | |
| /// list and the element type is known to be invariant so it can skip the | |
| /// type check. | |
| bool isNativeListInvariantAddInvocation(Class coreListClass) { | |
| if (isInvariant && name.text == 'add') { | |
| // The call to add is marked as invariant, so the type check on the | |
| // parameter to add is not needed. | |
| var receiver_ = receiver; | |
| if (receiver_ is VariableGet && | |
| receiver_.variable.isFinal && | |
| !receiver_.variable.isLate) { | |
| // The receiver is a final variable, so it only contains the | |
| // initializer value. Also, avoid late variables in case the CFE | |
| // lowering of late variables is changed in the future. | |
| var initializer = receiver_.variable.initializer; | |
| if (initializer is ListLiteral) { | |
| // The initializer is a list literal, so we know the list can be | |
| // grown, modified, and is represented by a JavaScript Array. | |
| return true; | |
| } | |
| if (initializer is StaticInvocation && | |
| initializer.target.enclosingClass == coreListClass && | |
| initializer.target.name.text == 'of' && | |
| initializer.arguments.named.isEmpty) { | |
| // The initializer is a `List.of()` call from the dart:core library | |
| // and the growable named argument has not been passed (it defaults | |
| // to true). | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| extension LibraryHelpers on Library { | |
| /// Returns the size in bytes of the Dart source code for this library. | |
| /// | |
| /// This includes the size of the library source and all part sources. | |
| int get dartSize { | |
| final uriToSource = enclosingComponent!.uriToSource; | |
| final libUri = fileUri; | |
| final source = uriToSource[libUri]; | |
| if (source == null) { | |
| // Sources that only contain external declarations have nothing to add to | |
| // the sum. | |
| return 0; | |
| } | |
| var dartSize = source.source.length; | |
| for (final part in parts) { | |
| var partUri = part.partUri; | |
| if (partUri.startsWith(importUri.scheme)) { | |
| // Convert to a relative-to-library uri in order to compute a file uri. | |
| partUri = p.relative(partUri, from: p.dirname('$importUri')); | |
| } | |
| final fileUri = libUri.resolve(partUri); | |
| final partSource = uriToSource[fileUri]; | |
| if (partSource == null) { | |
| // Sources that only contain external declarations have nothing to add | |
| // to the sum. | |
| continue; | |
| } | |
| dartSize += partSource.source.length; | |
| } | |
| return dartSize; | |
| } | |
| } |