| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2015, 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 part of js_backend; |
| 6 |
| 7 /** |
| 8 * Categorizes `noSuchMethod` implementations. |
| 9 * |
| 10 * If user code includes `noSuchMethod` implementations, type inference is |
| 11 * hindered because (for instance) any selector where the type of the |
| 12 * receiver is not known all implementations of `noSuchMethod` must be taken |
| 13 * into account when inferring the return type. |
| 14 * |
| 15 * The situation can be ameliorated with some heuristics for disregarding some |
| 16 * `noSuchMethod` implementations during type inference. We can partition |
| 17 * `noSuchMethod` implementations into 3 categories. |
| 18 * |
| 19 * Implementations in category A are the default implementations |
| 20 * `Object.noSuchMethod` and `Interceptor.noSuchMethod`. |
| 21 * |
| 22 * Implementations in category B syntactically immediately throw, for example: |
| 23 * |
| 24 * noSuchMethod(x) => throw 'not implemented' |
| 25 * |
| 26 * Implementations that do not fall into category A or B are in category C. They |
| 27 * are the only category of implementation that are considered during type |
| 28 * inference. |
| 29 * |
| 30 * Implementations that syntactically just forward to the super implementation, |
| 31 * for example: |
| 32 * |
| 33 * noSuchMethod(x) => super.noSuchMethod(x); |
| 34 * |
| 35 * are in the same category as the superclass implementation. This covers a |
| 36 * common case, where users implement `noSuchMethod` with these dummy |
| 37 * implementations to avoid warnings. |
| 38 */ |
| 39 class NoSuchMethodRegistry { |
| 40 /// The implementations that fall into category A, described above. |
| 41 final Set<FunctionElement> defaultImpls = new Set<FunctionElement>(); |
| 42 /// The implementations that fall into category B, described above. |
| 43 final Set<FunctionElement> throwingImpls = new Set<FunctionElement>(); |
| 44 /// The implementations that fall into category C, described above. |
| 45 final Set<Element> otherImpls = new Set<Element>(); |
| 46 |
| 47 /// The implementations that have not yet been categorized. |
| 48 final Set<Element> uncategorizedImpls = new Set<Element>(); |
| 49 |
| 50 final JavaScriptBackend backend; |
| 51 final Compiler compiler; |
| 52 |
| 53 NoSuchMethodRegistry(JavaScriptBackend backend) |
| 54 : this.backend = backend, |
| 55 this.compiler = backend.compiler; |
| 56 |
| 57 bool get hasThrowingNoSuchMethod => throwingImpls.isNotEmpty; |
| 58 bool get hasComplexNoSuchMethod => otherImpls.isNotEmpty; |
| 59 |
| 60 void registerNoSuchMethod(Element noSuchMethodElement) { |
| 61 uncategorizedImpls.add(noSuchMethodElement); |
| 62 } |
| 63 |
| 64 void onQueueEmpty() { |
| 65 uncategorizedImpls.forEach(_categorizeImpl); |
| 66 uncategorizedImpls.clear(); |
| 67 } |
| 68 |
| 69 NsmCategory _categorizeImpl(Element noSuchMethodElement) { |
| 70 assert(noSuchMethodElement.name == Compiler.NO_SUCH_METHOD); |
| 71 if (defaultImpls.contains(noSuchMethodElement)) { |
| 72 return NsmCategory.DEFAULT; |
| 73 } |
| 74 if (throwingImpls.contains(noSuchMethodElement)) { |
| 75 return NsmCategory.THROWING; |
| 76 } |
| 77 if (otherImpls.contains(noSuchMethodElement)) { |
| 78 return NsmCategory.OTHER; |
| 79 } |
| 80 if (noSuchMethodElement is! FunctionElement || |
| 81 !compiler.noSuchMethodSelector.signatureApplies(noSuchMethodElement)) { |
| 82 otherImpls.add(noSuchMethodElement); |
| 83 return NsmCategory.OTHER; |
| 84 } |
| 85 FunctionElement noSuchMethodFunc = noSuchMethodElement as FunctionElement; |
| 86 if (backend.isDefaultNoSuchMethodImplementation(noSuchMethodFunc)) { |
| 87 defaultImpls.add(noSuchMethodFunc); |
| 88 return NsmCategory.DEFAULT; |
| 89 } else if (hasForwardingSyntax(noSuchMethodFunc)) { |
| 90 // If the implementation is 'noSuchMethod(x) => super.noSuchMethod(x);' |
| 91 // then it is in the same category as the super call. |
| 92 Element superCall = noSuchMethodFunc.enclosingClass |
| 93 .lookupSuperSelector(compiler.noSuchMethodSelector); |
| 94 NsmCategory category = _categorizeImpl(superCall); |
| 95 switch(category) { |
| 96 case NsmCategory.DEFAULT: |
| 97 defaultImpls.add(noSuchMethodFunc); |
| 98 break; |
| 99 case NsmCategory.THROWING: |
| 100 throwingImpls.add(noSuchMethodFunc); |
| 101 break; |
| 102 case NsmCategory.OTHER: |
| 103 otherImpls.add(noSuchMethodFunc); |
| 104 break; |
| 105 } |
| 106 return category; |
| 107 } else if (isThrowing(noSuchMethodFunc)) { |
| 108 throwingImpls.add(noSuchMethodFunc); |
| 109 return NsmCategory.THROWING; |
| 110 } else { |
| 111 otherImpls.add(noSuchMethodFunc); |
| 112 return NsmCategory.OTHER; |
| 113 } |
| 114 } |
| 115 |
| 116 bool hasForwardingSyntax(FunctionElement element) { |
| 117 // At this point we know that this is signature-compatible with |
| 118 // Object.noSuchMethod, but it may have more than one argument as long as |
| 119 // it only has one required argument. |
| 120 String param = element.parameters.first.name; |
| 121 Statement body = element.node.body; |
| 122 Expression expr; |
| 123 if (body is Return && body.isArrowBody) { |
| 124 expr = body.expression; |
| 125 } else if (body is Block && |
| 126 !body.statements.isEmpty && |
| 127 body.statements.nodes.tail.isEmpty) { |
| 128 Statement stmt = body.statements.nodes.head; |
| 129 if (stmt is Return && stmt.hasExpression) { |
| 130 expr = stmt.expression; |
| 131 } |
| 132 } |
| 133 if (expr is Send && |
| 134 expr.isSuperCall && |
| 135 expr.selector is Identifier && |
| 136 (expr.selector as Identifier).source == Compiler.NO_SUCH_METHOD) { |
| 137 var arg = expr.arguments.head; |
| 138 if (expr.arguments.tail.isEmpty && |
| 139 arg is Send && |
| 140 arg.argumentsNode == null && |
| 141 arg.receiver == null && |
| 142 arg.selector is Identifier && |
| 143 arg.selector.source == param) { |
| 144 return true; |
| 145 } |
| 146 } |
| 147 return false; |
| 148 } |
| 149 |
| 150 bool isThrowing(FunctionElement element) { |
| 151 Statement body = element.node.body; |
| 152 if (body is Return && body.isArrowBody) { |
| 153 if (body.expression is Throw) { |
| 154 return true; |
| 155 } |
| 156 } else if (body is Block && |
| 157 !body.statements.isEmpty && |
| 158 body.statements.nodes.tail.isEmpty) { |
| 159 if (body.statements.nodes.head is Throw) { |
| 160 return true; |
| 161 } |
| 162 } |
| 163 return false; |
| 164 } |
| 165 } |
| 166 |
| 167 enum NsmCategory { DEFAULT, THROWING, OTHER } |
| OLD | NEW |