| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 | |
| 6 interface CallingContext { | |
| 7 MemberSet findMembers(String name); | |
| 8 CounterLog get counters(); | |
| 9 Library get library(); | |
| 10 bool get isStatic(); | |
| 11 MethodMember get method(); | |
| 12 | |
| 13 bool get needsCode(); | |
| 14 bool get showWarnings(); | |
| 15 | |
| 16 // Hopefully remove the 5 members below that are only used for code gen. | |
| 17 String _makeThisCode(); | |
| 18 | |
| 19 Value getTemp(Value value); | |
| 20 VariableValue forceTemp(Value value); | |
| 21 Value assignTemp(Value tmp, Value v); | |
| 22 void freeTemp(VariableValue value); | |
| 23 } | |
| 24 | |
| 25 // TODO(jimhug): Value needs better separation into three parts: | |
| 26 // 1. Static analysis | |
| 27 // 2. Type inferred abstract interpretation analysis | |
| 28 // 3. Actual code generation | |
| 29 /** | |
| 30 * This subtype of value is the one and only version used for static | |
| 31 * analysis. It has no code and its type is always the static type. | |
| 32 */ | |
| 33 class PureStaticValue extends Value { | |
| 34 bool isConst; | |
| 35 bool isType; | |
| 36 | |
| 37 // TODO(jimhug): Can we remove span? | |
| 38 PureStaticValue(Type type, SourceSpan span, | |
| 39 [this.isConst = false, this.isType = false]): | |
| 40 super(type, null, span); | |
| 41 | |
| 42 Member getMem(CallingContext context, String name, Node node) { | |
| 43 var member = type.getMember(name); | |
| 44 | |
| 45 if (member == null) { | |
| 46 world.warning('cannot find "$name" on "${type.name}"', node.span); | |
| 47 return null; | |
| 48 } | |
| 49 | |
| 50 if (isType && !member.isStatic) { | |
| 51 world.error('cannot refer to instance member as static', node.span); | |
| 52 } | |
| 53 | |
| 54 return member; | |
| 55 } | |
| 56 | |
| 57 Value get_(CallingContext context, String name, Node node) { | |
| 58 if (type.isVar) return new PureStaticValue(world.varType, node.span); | |
| 59 var member = getMem(context, name, node); | |
| 60 if (member == null) return new PureStaticValue(world.varType, node.span); | |
| 61 | |
| 62 return member._get(context, node, this); | |
| 63 } | |
| 64 | |
| 65 Value set_(CallingContext context, String name, Node node, Value value, | |
| 66 [int kind=0, int returnKind=ReturnKind.IGNORE]) { | |
| 67 if (type.isVar) return new PureStaticValue(world.varType, node.span); | |
| 68 | |
| 69 var member = getMem(context, name, node); | |
| 70 if (member != null) { | |
| 71 member._set(context, node, this, value); | |
| 72 } | |
| 73 return new PureStaticValue(value.type, node.span); | |
| 74 } | |
| 75 | |
| 76 Value setIndex(CallingContext context, Value index, Node node, Value value, | |
| 77 [int kind=0, int returnKind=ReturnKind.IGNORE]) { | |
| 78 var tmp = invoke(context, ':setindex', node, | |
| 79 new Arguments(null, [index, value])); | |
| 80 return new PureStaticValue(value.type, node.span); | |
| 81 } | |
| 82 | |
| 83 Value unop(int kind, CallingContext context, var node) { | |
| 84 switch (kind) { | |
| 85 case TokenKind.NOT: | |
| 86 // TODO(jimhug): Issue #359 seeks to clarify this behavior. | |
| 87 // ?var newVal = convertTo(context, world.nonNullBool); | |
| 88 return new PureStaticValue(world.boolType, node.span); | |
| 89 case TokenKind.ADD: | |
| 90 if (!isConst && !type.isNum) { | |
| 91 world.error('no unary add operator in dart', node.span); | |
| 92 } | |
| 93 return new PureStaticValue(world.numType, node.span); | |
| 94 case TokenKind.SUB: | |
| 95 return invoke(context, ':negate', node, Arguments.EMPTY); | |
| 96 case TokenKind.BIT_NOT: | |
| 97 return invoke(context, ':bit_not', node, Arguments.EMPTY); | |
| 98 } | |
| 99 world.internalError('unimplemented: ${node.op}', node.span); | |
| 100 } | |
| 101 | |
| 102 Value binop(int kind, Value other, CallingContext context, var node) { | |
| 103 var isConst = isConst && other.isConst; | |
| 104 | |
| 105 | |
| 106 switch (kind) { | |
| 107 case TokenKind.AND: | |
| 108 case TokenKind.OR: | |
| 109 return new PureStaticValue(world.boolType, node.span, isConst); | |
| 110 case TokenKind.EQ_STRICT: | |
| 111 return new PureStaticValue(world.boolType, node.span, isConst); | |
| 112 case TokenKind.NE_STRICT: | |
| 113 return new PureStaticValue(world.boolType, node.span, isConst); | |
| 114 } | |
| 115 | |
| 116 var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind); | |
| 117 var ret = invoke(context, name, node, new Arguments(null, [other])); | |
| 118 if (isConst) { | |
| 119 ret = new PureStaticValue(ret.type, node.span, isConst); | |
| 120 } | |
| 121 return ret; | |
| 122 } | |
| 123 | |
| 124 | |
| 125 Value invoke(CallingContext context, String name, Node node, | |
| 126 Arguments args) { | |
| 127 if (type.isVar) return new PureStaticValue(world.varType, node.span); | |
| 128 if (type.isFunction && name == ':call') { | |
| 129 return new PureStaticValue(world.varType, node.span); | |
| 130 } | |
| 131 | |
| 132 var member = getMem(context, name, node); | |
| 133 if (member == null) return new PureStaticValue(world.varType, node.span); | |
| 134 | |
| 135 return member.invoke(context, node, this, args); | |
| 136 } | |
| 137 | |
| 138 Value invokeNoSuchMethod(CallingContext context, String name, Node node, | |
| 139 [Arguments args]) { | |
| 140 if (isType) { | |
| 141 world.error('member lookup failed for "$name"', node.span); | |
| 142 } | |
| 143 | |
| 144 var member = getMem(context, 'noSuchMethod', node); | |
| 145 if (member == null) return new PureStaticValue(world.varType, node.span); | |
| 146 | |
| 147 final noSuchArgs = new Arguments(null, [ | |
| 148 new PureStaticValue(world.stringType, node.span), | |
| 149 new PureStaticValue(world.listType, node.span)]); | |
| 150 | |
| 151 return member.invoke(context, node, this, noSuchArgs); | |
| 152 } | |
| 153 | |
| 154 // These are implementation details of convertTo. (Eventually we might find it | |
| 155 // easier to just implement convertTo itself). | |
| 156 | |
| 157 Value _typeAssert(CallingContext context, Type toType) { | |
| 158 return _changeStaticType(toType); | |
| 159 } | |
| 160 | |
| 161 Value _changeStaticType(Type toType) { | |
| 162 if (toType === type) return this; | |
| 163 return new PureStaticValue(toType, span, isConst, isType); | |
| 164 } | |
| 165 } | |
| 166 | |
| 167 | |
| 168 /** | |
| 169 * Represents a meta-value for code generation. | |
| 170 */ | |
| 171 class Value { | |
| 172 /** The inferred (i.e. most precise) [Type] of the [Value]. */ | |
| 173 final Type type; | |
| 174 | |
| 175 /** The javascript code to generate this value. */ | |
| 176 final String code; | |
| 177 | |
| 178 /** The source location that created this value for error messages. */ | |
| 179 final SourceSpan span; | |
| 180 | |
| 181 Value(this.type, this.code, this.span) { | |
| 182 if (type == null) world.internalError('type passed as null', span); | |
| 183 } | |
| 184 | |
| 185 | |
| 186 /** Is this a pretend first-class type? */ | |
| 187 bool get isType() => false; | |
| 188 | |
| 189 /** Is this a reference to super? */ | |
| 190 bool get isSuper() => false; | |
| 191 | |
| 192 /** Is this value a constant expression? */ | |
| 193 bool get isConst() => false; | |
| 194 | |
| 195 /** Is this a final variable? */ | |
| 196 bool get isFinal() => false; | |
| 197 | |
| 198 /** If we reference this value multiple times, do we need a temp? */ | |
| 199 bool get needsTemp() => true; | |
| 200 | |
| 201 /** | |
| 202 * The statically declared [Type] of the [Value]. This type determines which | |
| 203 * kind of static type warnings are issued. It's also the type that is used | |
| 204 * for generating type assertions (i.e. given `Foo x; ...; x = expr;`, | |
| 205 * expr will be checked against "Foo" regardless of the inferred type of `x`). | |
| 206 */ | |
| 207 Type get staticType() => type; | |
| 208 | |
| 209 /** If [isConst], the [EvaluatedValue] that defines this value. */ | |
| 210 EvaluatedValue get constValue() => null; | |
| 211 | |
| 212 static Value comma(Value x, Value y) { | |
| 213 return new Value(y.type, '(${x.code}, ${y.code})', null); | |
| 214 } | |
| 215 | |
| 216 // TODO(jmesserly): more work is needed to make unifying all kinds of Values | |
| 217 // work properly. | |
| 218 static Value union(Value x, Value y) { | |
| 219 if (y === null || x == y) return x; | |
| 220 if (x === null) return y; | |
| 221 | |
| 222 var ret = x._tryUnion(y); | |
| 223 if (ret != null) return ret; | |
| 224 | |
| 225 // TODO(jmesserly): might want to call a _tryUnionReversed here. | |
| 226 ret = y._tryUnion(x); | |
| 227 if (ret != null) return ret; | |
| 228 | |
| 229 // TODO(jmesserly): should use something like UnionValue and track the | |
| 230 // precise set of types. For now we find the Type.union. | |
| 231 | |
| 232 // TODO(jmesserly): What to do about code? Right now, we're intentionally | |
| 233 // throwing it away because they aren't used in the current flow-insensitive | |
| 234 // inference. | |
| 235 return new Value(Type.union(x.type, y.type), null, null); | |
| 236 } | |
| 237 | |
| 238 Value _tryUnion(Value right) => null; | |
| 239 | |
| 240 // TODO(jimhug): remove once type system works better. | |
| 241 setField(Member field, Value value, [bool duringInit = false]) { } | |
| 242 | |
| 243 // Nothing to do in general? | |
| 244 validateInitialized(SourceSpan span) { } | |
| 245 | |
| 246 // TODO(jimhug): Fix these names once get/set are truly pseudo-keywords. | |
| 247 // See issue #379. | |
| 248 Value get_(CallingContext context, String name, Node node) { | |
| 249 final member = _resolveMember(context, name, node); | |
| 250 if (member != null) { | |
| 251 return member._get(context, node, this); | |
| 252 } else { | |
| 253 return invokeNoSuchMethod(context, 'get:$name', node); | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 Value set_(CallingContext context, String name, Node node, Value value, | |
| 258 [int kind=0, int returnKind=ReturnKind.IGNORE]) { | |
| 259 final member = _resolveMember(context, name, node); | |
| 260 if (member != null) { | |
| 261 var thisValue = this; | |
| 262 var thisTmp = null; | |
| 263 var retTmp = null; | |
| 264 if (kind != 0) { | |
| 265 // TODO(jimhug): Very special number optimizations will go here... | |
| 266 thisTmp = context.getTemp(thisValue); | |
| 267 thisValue = context.assignTemp(thisTmp, thisValue); | |
| 268 var lhs = member._get(context, node, thisTmp); | |
| 269 if (returnKind == ReturnKind.PRE) { | |
| 270 retTmp = context.forceTemp(lhs); | |
| 271 lhs = context.assignTemp(retTmp, lhs); | |
| 272 } | |
| 273 value = lhs.binop(kind, value, context, node); | |
| 274 } | |
| 275 | |
| 276 if (returnKind == ReturnKind.POST) { | |
| 277 // TODO(jimhug): Optimize this away when native JS is detected. | |
| 278 retTmp = context.forceTemp(value); | |
| 279 value = context.assignTemp(retTmp, value); | |
| 280 } | |
| 281 | |
| 282 var ret = member._set(context, node, thisValue, value); | |
| 283 if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp); | |
| 284 if (retTmp != null) { | |
| 285 context.freeTemp(retTmp); | |
| 286 return Value.comma(ret, retTmp); | |
| 287 } else { | |
| 288 return ret; | |
| 289 } | |
| 290 } else { | |
| 291 // TODO(jimhug): Need to support += and noSuchMethod better. | |
| 292 return invokeNoSuchMethod(context, 'set:$name', node, | |
| 293 new Arguments(null, [value])); | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 // TODO(jimhug): This method body has too much in common with set_ above. | |
| 298 Value setIndex(CallingContext context, Value index, Node node, Value value, | |
| 299 [int kind=0, int returnKind=ReturnKind.IGNORE]) { | |
| 300 final member = _resolveMember(context, ':setindex', node); | |
| 301 if (member != null) { | |
| 302 var thisValue = this; | |
| 303 var indexValue = index; | |
| 304 var thisTmp = null; | |
| 305 var indexTmp = null; | |
| 306 var retTmp = null; | |
| 307 if (returnKind == ReturnKind.POST) { | |
| 308 // TODO(jimhug): Optimize this away when native JS works. | |
| 309 retTmp = context.forceTemp(value); | |
| 310 } | |
| 311 if (kind != 0) { | |
| 312 // TODO(jimhug): Very special number optimizations will go here... | |
| 313 thisTmp = context.getTemp(this); | |
| 314 indexTmp = context.getTemp(index); | |
| 315 thisValue = context.assignTemp(thisTmp, thisValue); | |
| 316 indexValue = context.assignTemp(indexTmp, indexValue); | |
| 317 | |
| 318 if (returnKind == ReturnKind.PRE) { | |
| 319 retTmp = context.forceTemp(value); | |
| 320 } | |
| 321 | |
| 322 var lhs = thisTmp.invoke(context, ':index', node, | |
| 323 new Arguments(null, [indexTmp])); | |
| 324 if (returnKind == ReturnKind.PRE) { | |
| 325 lhs = context.assignTemp(retTmp, lhs); | |
| 326 } | |
| 327 value = lhs.binop(kind, value, context, node); | |
| 328 } | |
| 329 if (returnKind == ReturnKind.POST) { | |
| 330 value = context.assignTemp(retTmp, value); | |
| 331 } | |
| 332 | |
| 333 var ret = member.invoke(context, node, thisValue, | |
| 334 new Arguments(null, [indexValue, value])); | |
| 335 if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp); | |
| 336 if (indexTmp != null && indexTmp != index) context.freeTemp(indexTmp); | |
| 337 if (retTmp != null) { | |
| 338 context.freeTemp(retTmp); | |
| 339 return Value.comma(ret, retTmp); | |
| 340 } else { | |
| 341 return ret; | |
| 342 } | |
| 343 } else { | |
| 344 // TODO(jimhug): Need to support += and noSuchMethod better. | |
| 345 return invokeNoSuchMethod(context, ':index', node, | |
| 346 new Arguments(null, [index, value])); | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 //Value getIndex(CallingContext context, Value index, var node) { | |
| 351 //} | |
| 352 | |
| 353 Value unop(int kind, CallingContext context, var node) { | |
| 354 switch (kind) { | |
| 355 case TokenKind.NOT: | |
| 356 // TODO(jimhug): Issue #359 seeks to clarify this behavior. | |
| 357 var newVal = convertTo(context, world.nonNullBool); | |
| 358 return new Value(newVal.type, '!${newVal.code}', node.span); | |
| 359 case TokenKind.ADD: | |
| 360 world.error('no unary add operator in dart', node.span); | |
| 361 break; | |
| 362 case TokenKind.SUB: | |
| 363 return invoke(context, ':negate', node, Arguments.EMPTY); | |
| 364 case TokenKind.BIT_NOT: | |
| 365 return invoke(context, ':bit_not', node, Arguments.EMPTY); | |
| 366 } | |
| 367 world.internalError('unimplemented: ${node.op}', node.span); | |
| 368 } | |
| 369 | |
| 370 bool _mayOverrideEqual() { | |
| 371 // TODO(jimhug): Need to check subtypes as well | |
| 372 return type.isVar || type.isObject || | |
| 373 !type.getMember(':eq').declaringType.isObject; | |
| 374 } | |
| 375 | |
| 376 Value binop(int kind, Value other, CallingContext context, var node) { | |
| 377 switch (kind) { | |
| 378 case TokenKind.AND: | |
| 379 case TokenKind.OR: | |
| 380 final code = '${code} ${node.op} ${other.code}'; | |
| 381 return new Value(world.nonNullBool, code, node.span); | |
| 382 | |
| 383 case TokenKind.EQ_STRICT: | |
| 384 case TokenKind.NE_STRICT: | |
| 385 var op = kind == TokenKind.EQ_STRICT ? '==' : '!='; | |
| 386 if (code == 'null') { | |
| 387 return new Value(world.nonNullBool, | |
| 388 'null ${op} ${other.code}', node.span); | |
| 389 } else if (other.code == 'null') { | |
| 390 return new Value(world.nonNullBool, | |
| 391 'null ${op} ${code}', node.span); | |
| 392 } else { | |
| 393 // TODO(jimhug): Add check to see if we can just use op on this type | |
| 394 // TODO(jimhug): Optimize case of other.needsTemp == false. | |
| 395 var ret; | |
| 396 var check; | |
| 397 if (needsTemp) { | |
| 398 var tmp = context.forceTemp(this); | |
| 399 ret = tmp.code; | |
| 400 check = '(${ret} = ${code}) == null'; | |
| 401 } else { | |
| 402 ret = code; | |
| 403 check = 'null == ${code}'; | |
| 404 } | |
| 405 return new Value(world.nonNullBool, | |
| 406 '(${check} ? null ${op} (${other.code}) : ${ret} ${op}= ${other.code
})', | |
| 407 node.span); | |
| 408 } | |
| 409 | |
| 410 case TokenKind.EQ: | |
| 411 if (other.code == 'null') { | |
| 412 if (!_mayOverrideEqual()) { | |
| 413 return new Value(world.nonNullBool, '${code} == ${other.code}', | |
| 414 node.span); | |
| 415 } | |
| 416 } else if (code == 'null') { | |
| 417 return new Value(world.nonNullBool, '${code} == ${other.code}', | |
| 418 node.span); | |
| 419 } | |
| 420 break; | |
| 421 case TokenKind.NE: | |
| 422 if (other.code == 'null') { | |
| 423 if (!_mayOverrideEqual()) { | |
| 424 return new Value(world.nonNullBool, '${code} != ${other.code}', | |
| 425 node.span); | |
| 426 } | |
| 427 } else if (code == 'null') { | |
| 428 return new Value(world.nonNullBool, '${code} != ${other.code}', | |
| 429 node.span); | |
| 430 } | |
| 431 break; | |
| 432 | |
| 433 } | |
| 434 | |
| 435 var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind); | |
| 436 return invoke(context, name, node, new Arguments(null, [other])); | |
| 437 } | |
| 438 | |
| 439 | |
| 440 Value invoke(CallingContext context, String name, Node node, | |
| 441 Arguments args) { | |
| 442 // TODO(jmesserly): it'd be nice to remove these special cases | |
| 443 // We could create a :call in world members, and have that handle the | |
| 444 // canInvoke/Invoke logic. | |
| 445 | |
| 446 // Note: this check is a little different than the one in canInvoke, because | |
| 447 // sometimes we need to call dynamically even if we found the :call method | |
| 448 // statically. | |
| 449 | |
| 450 if (name == ':call') { | |
| 451 if (isType) { | |
| 452 world.error('must use "new" or "const" to construct a new instance', | |
| 453 node.span); | |
| 454 } | |
| 455 if (type.needsVarCall(args)) { | |
| 456 return _varCall(context, node, args); | |
| 457 } | |
| 458 } | |
| 459 | |
| 460 var member = _resolveMember(context, name, node); | |
| 461 if (member == null) { | |
| 462 return invokeNoSuchMethod(context, name, node, args); | |
| 463 } else { | |
| 464 return member.invoke(context, node, this, args); | |
| 465 } | |
| 466 } | |
| 467 | |
| 468 /** | |
| 469 * True if this class (or some related class that is not Object) overrides | |
| 470 * noSuchMethod. If it does we suppress warnings about unknown members. | |
| 471 */ | |
| 472 // TODO(jmesserly): should we be doing this? | |
| 473 bool _hasOverriddenNoSuchMethod() { | |
| 474 var m = type.getMember('noSuchMethod'); | |
| 475 return m != null && !m.declaringType.isObject; | |
| 476 } | |
| 477 | |
| 478 // TODO(jimhug): Handle more precise types here, i.e. consts or closed... | |
| 479 bool get isPreciseType() => isSuper || isType; | |
| 480 | |
| 481 void _missingMemberError(CallingContext context, String name, Node node) { | |
| 482 bool onStaticType = false; | |
| 483 if (type != staticType) { | |
| 484 onStaticType = staticType.getMember(name) !== null; | |
| 485 } | |
| 486 | |
| 487 if (!onStaticType && context.showWarnings && | |
| 488 !_isVarOrParameterType(staticType) && !_hasOverriddenNoSuchMethod()) { | |
| 489 // warn if the member was not found, or error if it is a static lookup. | |
| 490 var typeName = staticType.name; | |
| 491 if (typeName == null) typeName = staticType.library.name; | |
| 492 var message = 'cannot resolve "$name" on "${typeName}"'; | |
| 493 if (isType) { | |
| 494 world.error(message, node.span); | |
| 495 } else { | |
| 496 world.warning(message, node.span); | |
| 497 } | |
| 498 } | |
| 499 } | |
| 500 | |
| 501 | |
| 502 | |
| 503 MemberSet _tryResolveMember(CallingContext context, String name, Node node) { | |
| 504 var member = type.getMember(name); | |
| 505 if (member == null) { | |
| 506 _missingMemberError(context, name, node); | |
| 507 return null; | |
| 508 } else { | |
| 509 if (isType && !member.isStatic && context.showWarnings) { | |
| 510 world.error('cannot refer to instance member as static', node.span); | |
| 511 return null; | |
| 512 } | |
| 513 } | |
| 514 | |
| 515 if (isPreciseType || member.isStatic) { | |
| 516 return member.preciseMemberSet; | |
| 517 } else { | |
| 518 return member.potentialMemberSet; | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 // TODO(jmesserly): until reified generics are fixed, treat ParameterType as | |
| 523 // "var". | |
| 524 bool _isVarOrParameterType(Type t) => t.isVar || t is ParameterType; | |
| 525 | |
| 526 bool _shouldBindDynamically() { | |
| 527 return _isVarOrParameterType(type) || options.forceDynamic && !isConst; | |
| 528 } | |
| 529 | |
| 530 // TODO(jimhug): Better type here - currently is union(Member, MemberSet) | |
| 531 MemberSet _resolveMember(CallingContext context, String name, Node node) { | |
| 532 var member = null; | |
| 533 if (!_shouldBindDynamically()) { | |
| 534 member = _tryResolveMember(context, name, node); | |
| 535 } | |
| 536 | |
| 537 // Fall back to a dynamic operation for instance members | |
| 538 if (member == null && !isSuper && !isType) { | |
| 539 member = context.findMembers(name); | |
| 540 if (member == null && context.showWarnings) { | |
| 541 var where = 'the world'; | |
| 542 if (name.startsWith('_')) { | |
| 543 where = 'library "${context.library.name}"'; | |
| 544 } | |
| 545 world.warning('$name is not defined anywhere in $where.', | |
| 546 node.span); | |
| 547 } | |
| 548 } | |
| 549 | |
| 550 return member; | |
| 551 } | |
| 552 | |
| 553 checkFirstClass(SourceSpan span) { | |
| 554 if (isType) { | |
| 555 world.error('Types are not first class', span); | |
| 556 } | |
| 557 } | |
| 558 | |
| 559 /** Generate a call to an unknown function type. */ | |
| 560 Value _varCall(CallingContext context, Node node, Arguments args) { | |
| 561 // TODO(jmesserly): calls to unknown functions will bypass type checks, | |
| 562 // which normally happen on the caller side, or in the generated stub for | |
| 563 // dynamic method calls. What should we do? | |
| 564 var stub = world.functionType.getCallStub(args); | |
| 565 return stub.invoke(context, node, this, args); | |
| 566 } | |
| 567 | |
| 568 /** True if convertTo would generate a conversion. */ | |
| 569 bool needsConversion(Type toType) { | |
| 570 var c = convertTo(null, toType); | |
| 571 return c == null || code != c.code; | |
| 572 } | |
| 573 | |
| 574 /** | |
| 575 * Assign or convert this value to another type. | |
| 576 * This is used for converting between function types, inserting type | |
| 577 * checks when --enable_type_checks is enabled, and wrapping callback | |
| 578 * functions passed to the dom so we can restore their isolate context. | |
| 579 */ | |
| 580 Value convertTo(CallingContext context, Type toType) { | |
| 581 | |
| 582 // Issue type warnings unless we are processing a dynamic operation. | |
| 583 bool checked = context != null && context.showWarnings; | |
| 584 | |
| 585 var callMethod = toType.getCallMethod(); | |
| 586 if (callMethod != null) { | |
| 587 if (checked && !toType.isAssignable(type)) { | |
| 588 convertWarning(toType); | |
| 589 } | |
| 590 | |
| 591 return _maybeWrapFunction(toType, callMethod); | |
| 592 } | |
| 593 | |
| 594 // If we're assigning from a var, pretend it's Object for the purpose of | |
| 595 // runtime checks. | |
| 596 | |
| 597 // TODO(jmesserly): I'm a little bothered by the fact that we can't call | |
| 598 // isSubtypeOf directly. If we tracked null literals as the bottom type, | |
| 599 // and then only allowed Dynamic to be bottom for generic type args, I think | |
| 600 // we'd get the right behavior from isSubtypeOf. | |
| 601 Type fromType = type; | |
| 602 if (type.isVar && (code != 'null' || !toType.isNullable)) { | |
| 603 fromType = world.objectType; | |
| 604 } | |
| 605 | |
| 606 // TODO(jmesserly): remove the special case for "num" when our num handling | |
| 607 // is better. | |
| 608 bool bothNum = type.isNum && toType.isNum; | |
| 609 if (!fromType.isSubtypeOf(toType) && !bothNum) { | |
| 610 // If it is a narrowing conversion, we'll need a check in checked mode. | |
| 611 | |
| 612 if (checked && !toType.isSubtypeOf(type)) { | |
| 613 // According to the static types, this conversion can't work. | |
| 614 convertWarning(toType); | |
| 615 } | |
| 616 | |
| 617 if (options.enableTypeChecks) { | |
| 618 if (context == null) { | |
| 619 // If we're called from needsConversion, we don't need a context. | |
| 620 // Just return null so it knows a conversion is required. | |
| 621 return null; | |
| 622 } | |
| 623 return _typeAssert(context, toType); | |
| 624 } | |
| 625 } | |
| 626 | |
| 627 return _changeStaticType(toType); | |
| 628 } | |
| 629 | |
| 630 // Nothing to do in general. | |
| 631 Value _changeStaticType(Type toType) => this; | |
| 632 | |
| 633 /** | |
| 634 * Wraps a function with a conversion, so it can be called directly from | |
| 635 * Dart or JS code with the proper arity. We avoid the wrapping if the target | |
| 636 * function has the same arity. | |
| 637 * | |
| 638 * Also wraps a callback attached to the dom (e.g. event listeners, | |
| 639 * setTimeout) so we can restore it's isolate context information. This is | |
| 640 * needed so that callbacks are executed within the context of the isolate | |
| 641 * that created them in the first place. | |
| 642 */ | |
| 643 Value _maybeWrapFunction(Type toType, MethodMember callMethod) { | |
| 644 int arity = callMethod.parameters.length; | |
| 645 var myCall = type.getCallMethod(); | |
| 646 | |
| 647 Value result = this; | |
| 648 if (myCall == null || myCall.parameters.length != arity) { | |
| 649 final stub = world.functionType.getCallStub(new Arguments.bare(arity)); | |
| 650 result = new Value(toType, 'to\$${stub.name}($code)', span); | |
| 651 } | |
| 652 | |
| 653 // TODO(jmesserly): handle when type or toType are type parameters. | |
| 654 if (toType.library.isDomOrHtml && !type.library.isDomOrHtml) { | |
| 655 // TODO(jmesserly): either remove this or make it a more first class | |
| 656 // feature of our native interop. We shouldn't be checking for the DOM | |
| 657 // library--any host environment (like node.js) might need this feature | |
| 658 // for isolates too. But we don't want to wrap every function we send to | |
| 659 // native code--many callbacks like List.filter are perfectly safe. | |
| 660 if (arity == 0) { | |
| 661 world.gen.corejs.useWrap0 = true; | |
| 662 } else if (arity == 1) { | |
| 663 world.gen.corejs.useWrap1 = true; | |
| 664 } else if (arity == 2) { | |
| 665 world.gen.corejs.useWrap2 = true; | |
| 666 } | |
| 667 | |
| 668 result = new Value(toType, '\$wrap_call\$$arity(${result.code})', span); | |
| 669 } | |
| 670 | |
| 671 return result._changeStaticType(toType); | |
| 672 } | |
| 673 | |
| 674 /** | |
| 675 * Generates a run time type assertion for the given value. This works like | |
| 676 * [instanceOf], but it allows null since Dart types are nullable. | |
| 677 * Also it will throw a TypeError if it gets the wrong type. | |
| 678 */ | |
| 679 Value _typeAssert(CallingContext context, Type toType) { | |
| 680 if (toType is ParameterType) { | |
| 681 ParameterType p = toType; | |
| 682 toType = p.extendsType; | |
| 683 } | |
| 684 | |
| 685 if (toType.isObject || toType.isVar) { | |
| 686 world.internalError( | |
| 687 'We thought ${type.name} is not a subtype of ${toType.name}?'); | |
| 688 } | |
| 689 | |
| 690 // Prevent a stack overflow when forceDynamic and type checks are both | |
| 691 // enabled. forceDynamic would cause the TypeError constructor to type check | |
| 692 // its arguments, which in turn invokes the TypeError constructor, ad | |
| 693 // infinitum. | |
| 694 String throwTypeError(String paramName) => world.withoutForceDynamic(() { | |
| 695 final typeErrorCtor = world.typeErrorType.getConstructor('_internal'); | |
| 696 world.gen.corejs.ensureTypeNameOf(); | |
| 697 final result = typeErrorCtor.invoke(context, null, | |
| 698 new TypeValue(world.typeErrorType, null), | |
| 699 new Arguments(null, [ | |
| 700 new Value(world.objectType, paramName, null), | |
| 701 new Value(world.stringType, '"${toType.name}"', null)])); | |
| 702 world.gen.corejs.useThrow = true; | |
| 703 return '\$throw(${result.code})'; | |
| 704 }); | |
| 705 | |
| 706 // TODO(jmesserly): better assert for integers? | |
| 707 if (toType.isNum) toType = world.numType; | |
| 708 | |
| 709 // Generate a check like these: | |
| 710 // obj && obj.is$TypeName() | |
| 711 // $assert_int(obj) | |
| 712 // | |
| 713 // We rely on the fact that calling an undefined method produces a JS | |
| 714 // TypeError. Alternatively we could define fallbacks on Object that throw. | |
| 715 String check; | |
| 716 if (toType.isVoid) { | |
| 717 check = '\$assert_void($code)'; | |
| 718 if (toType.typeCheckCode == null) { | |
| 719 toType.typeCheckCode = ''' | |
| 720 function \$assert_void(x) { | |
| 721 if (x == null) return null; | |
| 722 ${throwTypeError("x")} | |
| 723 }'''; | |
| 724 } | |
| 725 } else if (toType == world.nonNullBool) { | |
| 726 // This could be made less of a special case | |
| 727 world.gen.corejs.useNotNullBool = true; | |
| 728 check = '\$notnull_bool($code)'; | |
| 729 | |
| 730 } else if (toType.library.isCore && toType.typeofName != null) { | |
| 731 check = '\$assert_${toType.name}($code)'; | |
| 732 | |
| 733 if (toType.typeCheckCode == null) { | |
| 734 toType.typeCheckCode = ''' | |
| 735 function \$assert_${toType.name}(x) { | |
| 736 if (x == null || typeof(x) == "${toType.typeofName}") return x; | |
| 737 ${throwTypeError("x")} | |
| 738 }'''; | |
| 739 } | |
| 740 } else { | |
| 741 toType.isChecked = true; | |
| 742 | |
| 743 String checkName = 'assert\$${toType.jsname}'; | |
| 744 | |
| 745 // If we track nullability, we could simplify this check. | |
| 746 var temp = context.getTemp(this); | |
| 747 check = '(${context.assignTemp(temp, this).code} == null ? null :'; | |
| 748 check += ' ${temp.code}.$checkName())'; | |
| 749 if (this != temp) context.freeTemp(temp); | |
| 750 | |
| 751 // Generate the fallback on Object (that throws a TypeError) | |
| 752 world.objectType.varStubs.putIfAbsent(checkName, | |
| 753 () => new VarMethodStub(checkName, null, Arguments.EMPTY, | |
| 754 throwTypeError('this'))); | |
| 755 } | |
| 756 | |
| 757 context.counters.typeAsserts++; | |
| 758 return new Value(toType, check, span); | |
| 759 } | |
| 760 | |
| 761 /** | |
| 762 * Test to see if value is an instance of this type. | |
| 763 * | |
| 764 * - If a primitive type, then uses the JavaScript typeof. | |
| 765 * - If it's a non-generic class, use instanceof. | |
| 766 * - Otherwise add a fake member to test for. This value is generated | |
| 767 * as a function so that it can be called for a runtime failure. | |
| 768 */ | |
| 769 Value instanceOf(CallingContext context, Type toType, SourceSpan span, | |
| 770 [bool isTrue=true, bool forceCheck=false]) { | |
| 771 // TODO(jimhug): Optimize away tests that will always pass unless | |
| 772 // forceCheck is true. | |
| 773 | |
| 774 if (toType.isVar) { | |
| 775 world.error('cannot resolve type', span); | |
| 776 } | |
| 777 | |
| 778 String testCode = null; | |
| 779 if (toType.isVar || toType.isObject || toType is ParameterType) { | |
| 780 // Note: everything is an Object, including null. | |
| 781 if (needsTemp) { | |
| 782 return new Value(world.nonNullBool, '($code, true)', span); | |
| 783 } else { | |
| 784 // TODO(jimhug): Mark non-const? | |
| 785 return Value.fromBool(true, span); | |
| 786 } | |
| 787 } | |
| 788 | |
| 789 if (toType.library.isCore) { | |
| 790 var typeofName = toType.typeofName; | |
| 791 if (typeofName != null) { | |
| 792 testCode = "(typeof($code) ${isTrue ? '==' : '!='} '$typeofName')"; | |
| 793 } | |
| 794 } | |
| 795 | |
| 796 if (toType.isClass | |
| 797 && !toType.isHiddenNativeType && !toType.isConcreteGeneric) { | |
| 798 toType.markUsed(); | |
| 799 testCode = '($code instanceof ${toType.jsname})'; | |
| 800 if (!isTrue) { | |
| 801 testCode = '!${testCode}'; | |
| 802 } | |
| 803 } | |
| 804 if (testCode == null) { | |
| 805 toType.isTested = true; | |
| 806 | |
| 807 // If we track nullability, we could simplify this check. | |
| 808 var temp = context.getTemp(this); | |
| 809 | |
| 810 String checkName = 'is\$${toType.jsname}'; | |
| 811 testCode = '(${context.assignTemp(temp, this).code} &&' | |
| 812 ' ${temp.code}.$checkName())'; | |
| 813 if (isTrue) { | |
| 814 // Add !! to convert to boolean. | |
| 815 // TODO(jimhug): only do this if needed | |
| 816 testCode = '!!${testCode}'; | |
| 817 } else { | |
| 818 // The single ! here nicely converts undefined to false and function | |
| 819 // to true. | |
| 820 testCode = '!${testCode}'; | |
| 821 } | |
| 822 if (this != temp) context.freeTemp(temp); | |
| 823 | |
| 824 // Generate the fallback on Object (that returns false) | |
| 825 if (!world.objectType.varStubs.containsKey(checkName)) { | |
| 826 world.objectType.varStubs[checkName] = | |
| 827 new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false'); | |
| 828 } | |
| 829 } | |
| 830 return new Value(world.nonNullBool, testCode, span); | |
| 831 } | |
| 832 | |
| 833 void convertWarning(Type toType) { | |
| 834 // TODO(jmesserly): better error messages for type conversion failures | |
| 835 world.warning( | |
| 836 'type "${type.fullname}" is not assignable to "${toType.fullname}"', | |
| 837 span); | |
| 838 } | |
| 839 | |
| 840 Value invokeNoSuchMethod(CallingContext context, String name, Node node, | |
| 841 [Arguments args]) { | |
| 842 if (isType) { | |
| 843 world.error('member lookup failed for "$name"', node.span); | |
| 844 } | |
| 845 | |
| 846 var pos = ''; | |
| 847 if (args != null) { | |
| 848 var argsCode = []; | |
| 849 for (int i = 0; i < args.length; i++) { | |
| 850 argsCode.add(args.values[i].code); | |
| 851 } | |
| 852 pos = Strings.join(argsCode, ", "); // don't remove trailing nulls | |
| 853 } | |
| 854 final noSuchArgs = [ | |
| 855 new Value(world.stringType, '"$name"', node.span), | |
| 856 new Value(world.listType, '[$pos]', node.span)]; | |
| 857 | |
| 858 // TODO(jmesserly): should be passing names but that breaks tests. Oh well. | |
| 859 /*if (args != null && args.hasNames) { | |
| 860 var names = []; | |
| 861 for (int i = args.bareCount; i < args.length; i++) { | |
| 862 names.add('"${args.getName(i)}", ${args.values[i].code}'); | |
| 863 } | |
| 864 noSuchArgs.add(new Value(world.gen.useMapFactory(), | |
| 865 '\$map(${Strings.join(names, ", ")})')); | |
| 866 }*/ | |
| 867 | |
| 868 // Finally, invoke noSuchMethod | |
| 869 return _resolveMember(context, 'noSuchMethod', node).invoke( | |
| 870 context, node, this, new Arguments(null, noSuchArgs)); | |
| 871 } | |
| 872 | |
| 873 | |
| 874 static Value fromBool(bool value, SourceSpan span) { | |
| 875 return new BoolValue(value, true, span); | |
| 876 } | |
| 877 | |
| 878 static Value fromInt(int value, SourceSpan span) { | |
| 879 return new IntValue(value, true, span); | |
| 880 } | |
| 881 | |
| 882 static Value fromDouble(double value, SourceSpan span) { | |
| 883 return new DoubleValue(value, true, span); | |
| 884 } | |
| 885 | |
| 886 static Value fromString(String value, SourceSpan span) { | |
| 887 return new StringValue(value, true, span); | |
| 888 } | |
| 889 | |
| 890 static Value fromNull(SourceSpan span) { | |
| 891 return new NullValue(true, span); | |
| 892 } | |
| 893 } | |
| 894 | |
| 895 | |
| 896 // TODO(jimhug): rename to PrimitiveValue and refactor further | |
| 897 class EvaluatedValue extends Value implements Hashable { | |
| 898 /** Is this value treated as const by dart language? */ | |
| 899 final bool isConst; | |
| 900 | |
| 901 EvaluatedValue(this.isConst, Type type, SourceSpan span): | |
| 902 super(type, '@@@', span); | |
| 903 | |
| 904 String get code() { | |
| 905 world.internalError('Should not be getting code from raw EvaluatedValue', | |
| 906 span); | |
| 907 } | |
| 908 | |
| 909 get actualValue() { | |
| 910 world.internalError('Should not be getting actual value ' | |
| 911 'from raw EvaluatedValue', span); | |
| 912 } | |
| 913 | |
| 914 bool get needsTemp() => false; | |
| 915 | |
| 916 EvaluatedValue get constValue() => this; | |
| 917 | |
| 918 // TODO(jimhug): Using computed code here without caching is major fear. | |
| 919 int hashCode() => code.hashCode(); | |
| 920 | |
| 921 bool operator ==(var other) { | |
| 922 return other is EvaluatedValue && other.type == this.type && | |
| 923 other.code == this.code; | |
| 924 } | |
| 925 } | |
| 926 | |
| 927 | |
| 928 class NullValue extends EvaluatedValue { | |
| 929 NullValue(bool isConst, SourceSpan span): | |
| 930 super(isConst, world.varType, span); | |
| 931 | |
| 932 get actualValue() => null; | |
| 933 | |
| 934 String get code() => 'null'; | |
| 935 | |
| 936 Value binop(int kind, var other, CallingContext context, var node) { | |
| 937 if (other is! NullValue) return super.binop(kind, other, context, node); | |
| 938 | |
| 939 final c = isConst && other.isConst; | |
| 940 final s = node.span; | |
| 941 switch (kind) { | |
| 942 case TokenKind.EQ_STRICT: | |
| 943 case TokenKind.EQ: | |
| 944 return new BoolValue(true, c, s); | |
| 945 case TokenKind.NE_STRICT: | |
| 946 case TokenKind.NE: | |
| 947 return new BoolValue(false, c, s); | |
| 948 } | |
| 949 | |
| 950 return super.binop(kind, other, context, node); | |
| 951 } | |
| 952 } | |
| 953 | |
| 954 class BoolValue extends EvaluatedValue { | |
| 955 final bool actualValue; | |
| 956 | |
| 957 BoolValue(this.actualValue, bool isConst, SourceSpan span): | |
| 958 super(isConst, world.nonNullBool, span); | |
| 959 | |
| 960 String get code() => actualValue ? 'true' : 'false'; | |
| 961 | |
| 962 Value unop(int kind, CallingContext context, var node) { | |
| 963 switch (kind) { | |
| 964 case TokenKind.NOT: | |
| 965 return new BoolValue(!actualValue, isConst, node.span); | |
| 966 } | |
| 967 return super.unop(kind, context, node); | |
| 968 } | |
| 969 | |
| 970 Value binop(int kind, var other, CallingContext context, var node) { | |
| 971 if (other is! BoolValue) return super.binop(kind, other, context, node); | |
| 972 | |
| 973 final c = isConst && other.isConst; | |
| 974 final s = node.span; | |
| 975 bool x = actualValue, y = other.actualValue; | |
| 976 switch (kind) { | |
| 977 case TokenKind.EQ_STRICT: | |
| 978 case TokenKind.EQ: | |
| 979 return new BoolValue(x == y, c, s); | |
| 980 case TokenKind.NE_STRICT: | |
| 981 case TokenKind.NE: | |
| 982 return new BoolValue(x != y, c, s); | |
| 983 case TokenKind.AND: | |
| 984 return new BoolValue(x && y, c, s); | |
| 985 case TokenKind.OR: | |
| 986 return new BoolValue(x || y, c, s); | |
| 987 } | |
| 988 | |
| 989 return super.binop(kind, other, context, node); | |
| 990 } | |
| 991 } | |
| 992 | |
| 993 class IntValue extends EvaluatedValue { | |
| 994 final int actualValue; | |
| 995 | |
| 996 IntValue(this.actualValue, bool isConst, SourceSpan span): | |
| 997 super(isConst, world.intType, span); | |
| 998 | |
| 999 // TODO(jimhug): Only add parens when needed. | |
| 1000 String get code() => '(${actualValue})'; | |
| 1001 | |
| 1002 Value unop(int kind, CallingContext context, var node) { | |
| 1003 switch (kind) { | |
| 1004 case TokenKind.ADD: | |
| 1005 // This is allowed on numeric constants only | |
| 1006 return new IntValue(actualValue, isConst, span); | |
| 1007 case TokenKind.SUB: | |
| 1008 return new IntValue(-actualValue, isConst, span); | |
| 1009 case TokenKind.BIT_NOT: | |
| 1010 return new IntValue(~actualValue, isConst, span); | |
| 1011 } | |
| 1012 return super.unop(kind, context, node); | |
| 1013 } | |
| 1014 | |
| 1015 | |
| 1016 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1017 final c = isConst && other.isConst; | |
| 1018 final s = node.span; | |
| 1019 if (other is IntValue) { | |
| 1020 int x = actualValue; | |
| 1021 int y = other.actualValue; | |
| 1022 switch (kind) { | |
| 1023 case TokenKind.EQ_STRICT: | |
| 1024 case TokenKind.EQ: | |
| 1025 return new BoolValue(x == y, c, s); | |
| 1026 case TokenKind.NE_STRICT: | |
| 1027 case TokenKind.NE: | |
| 1028 return new BoolValue(x != y, c, s); | |
| 1029 | |
| 1030 case TokenKind.BIT_OR: | |
| 1031 return new IntValue(x | y, c, s); | |
| 1032 case TokenKind.BIT_XOR: | |
| 1033 return new IntValue(x ^ y, c, s); | |
| 1034 case TokenKind.BIT_AND: | |
| 1035 return new IntValue(x & y, c, s); | |
| 1036 case TokenKind.SHL: | |
| 1037 return new IntValue(x << y, c, s); | |
| 1038 case TokenKind.SAR: | |
| 1039 return new IntValue(x >> y, c, s); | |
| 1040 case TokenKind.ADD: | |
| 1041 return new IntValue(x + y, c, s); | |
| 1042 case TokenKind.SUB: | |
| 1043 return new IntValue(x - y, c, s); | |
| 1044 case TokenKind.MUL: | |
| 1045 return new IntValue(x * y, c, s); | |
| 1046 case TokenKind.DIV: | |
| 1047 return new DoubleValue(x / y, c, s); | |
| 1048 case TokenKind.TRUNCDIV: | |
| 1049 return new IntValue(x ~/ y, c, s); | |
| 1050 case TokenKind.MOD: | |
| 1051 return new IntValue(x % y, c, s); | |
| 1052 case TokenKind.LT: | |
| 1053 return new BoolValue(x < y, c, s); | |
| 1054 case TokenKind.GT: | |
| 1055 return new BoolValue(x > y, c, s); | |
| 1056 case TokenKind.LTE: | |
| 1057 return new BoolValue(x <= y, c, s); | |
| 1058 case TokenKind.GTE: | |
| 1059 return new BoolValue(x >= y, c, s); | |
| 1060 } | |
| 1061 } else if (other is DoubleValue) { | |
| 1062 int x = actualValue; | |
| 1063 double y = other.actualValue; | |
| 1064 switch (kind) { | |
| 1065 case TokenKind.EQ_STRICT: | |
| 1066 case TokenKind.EQ: | |
| 1067 return new BoolValue(x == y, c, s); | |
| 1068 case TokenKind.NE_STRICT: | |
| 1069 case TokenKind.NE: | |
| 1070 return new BoolValue(x != y, c, s); | |
| 1071 | |
| 1072 case TokenKind.ADD: | |
| 1073 return new DoubleValue(x + y, c, s); | |
| 1074 case TokenKind.SUB: | |
| 1075 return new DoubleValue(x - y, c, s); | |
| 1076 case TokenKind.MUL: | |
| 1077 return new DoubleValue(x * y, c, s); | |
| 1078 case TokenKind.DIV: | |
| 1079 return new DoubleValue(x / y, c, s); | |
| 1080 case TokenKind.TRUNCDIV: | |
| 1081 // TODO(jimhug): I expected int, but corelib says double here... | |
| 1082 return new DoubleValue(x ~/ y, c, s); | |
| 1083 case TokenKind.MOD: | |
| 1084 return new DoubleValue(x % y, c, s); | |
| 1085 case TokenKind.LT: | |
| 1086 return new BoolValue(x < y, c, s); | |
| 1087 case TokenKind.GT: | |
| 1088 return new BoolValue(x > y, c, s); | |
| 1089 case TokenKind.LTE: | |
| 1090 return new BoolValue(x <= y, c, s); | |
| 1091 case TokenKind.GTE: | |
| 1092 return new BoolValue(x >= y, c, s); | |
| 1093 } | |
| 1094 } | |
| 1095 | |
| 1096 return super.binop(kind, other, context, node); | |
| 1097 } | |
| 1098 } | |
| 1099 | |
| 1100 class DoubleValue extends EvaluatedValue { | |
| 1101 final double actualValue; | |
| 1102 | |
| 1103 DoubleValue(this.actualValue, bool isConst, SourceSpan span): | |
| 1104 super(isConst, world.doubleType, span); | |
| 1105 | |
| 1106 String get code() => '(${actualValue})'; | |
| 1107 | |
| 1108 Value unop(int kind, CallingContext context, var node) { | |
| 1109 switch (kind) { | |
| 1110 case TokenKind.ADD: | |
| 1111 // This is allowed on numeric constants only | |
| 1112 return new DoubleValue(actualValue, isConst, span); | |
| 1113 case TokenKind.SUB: | |
| 1114 return new DoubleValue(-actualValue, isConst, span); | |
| 1115 } | |
| 1116 return super.unop(kind, context, node); | |
| 1117 } | |
| 1118 | |
| 1119 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1120 final c = isConst && other.isConst; | |
| 1121 final s = node.span; | |
| 1122 if (other is DoubleValue) { | |
| 1123 double x = actualValue; | |
| 1124 double y = other.actualValue; | |
| 1125 switch (kind) { | |
| 1126 case TokenKind.EQ_STRICT: | |
| 1127 case TokenKind.EQ: | |
| 1128 return new BoolValue(x == y, c, s); | |
| 1129 case TokenKind.NE_STRICT: | |
| 1130 case TokenKind.NE: | |
| 1131 return new BoolValue(x != y, c, s); | |
| 1132 | |
| 1133 case TokenKind.ADD: | |
| 1134 return new DoubleValue(x + y, c, s); | |
| 1135 case TokenKind.SUB: | |
| 1136 return new DoubleValue(x - y, c, s); | |
| 1137 case TokenKind.MUL: | |
| 1138 return new DoubleValue(x * y, c, s); | |
| 1139 case TokenKind.DIV: | |
| 1140 return new DoubleValue(x / y, c, s); | |
| 1141 case TokenKind.TRUNCDIV: | |
| 1142 // TODO(jimhug): I expected int, but corelib says double here... | |
| 1143 return new DoubleValue(x ~/ y, c, s); | |
| 1144 case TokenKind.MOD: | |
| 1145 return new DoubleValue(x % y, c, s); | |
| 1146 case TokenKind.LT: | |
| 1147 return new BoolValue(x < y, c, s); | |
| 1148 case TokenKind.GT: | |
| 1149 return new BoolValue(x > y, c, s); | |
| 1150 case TokenKind.LTE: | |
| 1151 return new BoolValue(x <= y, c, s); | |
| 1152 case TokenKind.GTE: | |
| 1153 return new BoolValue(x >= y, c, s); | |
| 1154 } | |
| 1155 } else if (other is IntValue) { | |
| 1156 double x = actualValue; | |
| 1157 int y = other.actualValue; | |
| 1158 switch (kind) { | |
| 1159 case TokenKind.EQ_STRICT: | |
| 1160 case TokenKind.EQ: | |
| 1161 return new BoolValue(x == y, c, s); | |
| 1162 case TokenKind.NE_STRICT: | |
| 1163 case TokenKind.NE: | |
| 1164 return new BoolValue(x != y, c, s); | |
| 1165 | |
| 1166 case TokenKind.ADD: | |
| 1167 return new DoubleValue(x + y, c, s); | |
| 1168 case TokenKind.SUB: | |
| 1169 return new DoubleValue(x - y, c, s); | |
| 1170 case TokenKind.MUL: | |
| 1171 return new DoubleValue(x * y, c, s); | |
| 1172 case TokenKind.DIV: | |
| 1173 return new DoubleValue(x / y, c, s); | |
| 1174 case TokenKind.TRUNCDIV: | |
| 1175 // TODO(jimhug): I expected int, but corelib says double here... | |
| 1176 return new DoubleValue(x ~/ y, c, s); | |
| 1177 case TokenKind.MOD: | |
| 1178 return new DoubleValue(x % y, c, s); | |
| 1179 case TokenKind.LT: | |
| 1180 return new BoolValue(x < y, c, s); | |
| 1181 case TokenKind.GT: | |
| 1182 return new BoolValue(x > y, c, s); | |
| 1183 case TokenKind.LTE: | |
| 1184 return new BoolValue(x <= y, c, s); | |
| 1185 case TokenKind.GTE: | |
| 1186 return new BoolValue(x >= y, c, s); | |
| 1187 } | |
| 1188 } | |
| 1189 | |
| 1190 return super.binop(kind, other, context, node); | |
| 1191 } | |
| 1192 } | |
| 1193 | |
| 1194 class StringValue extends EvaluatedValue { | |
| 1195 final String actualValue; | |
| 1196 | |
| 1197 StringValue(this.actualValue, bool isConst, SourceSpan span): | |
| 1198 super(isConst, world.stringType, span); | |
| 1199 | |
| 1200 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1201 if (other is! StringValue) return super.binop(kind, other, context, node); | |
| 1202 | |
| 1203 final c = isConst && other.isConst; | |
| 1204 final s = node.span; | |
| 1205 String x = actualValue, y = other.actualValue; | |
| 1206 switch (kind) { | |
| 1207 case TokenKind.EQ_STRICT: | |
| 1208 case TokenKind.EQ: | |
| 1209 return new BoolValue(x == y, c, s); | |
| 1210 case TokenKind.NE_STRICT: | |
| 1211 case TokenKind.NE: | |
| 1212 return new BoolValue(x != y, c, s); | |
| 1213 case TokenKind.ADD: | |
| 1214 return new StringValue(x + y, c, s); | |
| 1215 } | |
| 1216 | |
| 1217 return super.binop(kind, other, context, node); | |
| 1218 } | |
| 1219 | |
| 1220 | |
| 1221 // This is expensive and we may want to cache its value if called often | |
| 1222 String get code() { | |
| 1223 // TODO(jimhug): This could be much more efficient | |
| 1224 StringBuffer buf = new StringBuffer(); | |
| 1225 buf.add('"'); | |
| 1226 for (int i=0; i < actualValue.length; i++) { | |
| 1227 var ch = actualValue.charCodeAt(i); | |
| 1228 switch (ch) { | |
| 1229 case 9/*'\t'*/: buf.add(@'\t'); break; | |
| 1230 case 10/*'\n'*/: buf.add(@'\n'); break; | |
| 1231 case 13/*'\r'*/: buf.add(@'\r'); break; | |
| 1232 case 34/*"*/: buf.add(@'\"'); break; | |
| 1233 case 92/*\*/: buf.add(@'\\'); break; | |
| 1234 default: | |
| 1235 if (ch >= 32 && ch <= 126) { | |
| 1236 buf.add(actualValue[i]); | |
| 1237 } else { | |
| 1238 final hex = ch.toRadixString(16); | |
| 1239 switch (hex.length) { | |
| 1240 case 1: buf.add(@'\x0'); buf.add(hex); break; | |
| 1241 case 2: buf.add(@'\x'); buf.add(hex); break; | |
| 1242 case 3: buf.add(@'\u0'); buf.add(hex); break; | |
| 1243 case 4: buf.add(@'\u'); buf.add(hex); break; | |
| 1244 default: | |
| 1245 world.internalError( | |
| 1246 'unicode values greater than 2 bytes not implemented'); | |
| 1247 break; | |
| 1248 } | |
| 1249 } | |
| 1250 break; | |
| 1251 } | |
| 1252 } | |
| 1253 buf.add('"'); | |
| 1254 return buf.toString(); | |
| 1255 } | |
| 1256 } | |
| 1257 | |
| 1258 class ListValue extends EvaluatedValue { | |
| 1259 final List<Value> values; | |
| 1260 | |
| 1261 ListValue(this.values, bool isConst, Type type, SourceSpan span): | |
| 1262 super(isConst, type, span); | |
| 1263 | |
| 1264 String get code() { | |
| 1265 final buf = new StringBuffer(); | |
| 1266 buf.add('['); | |
| 1267 for (var i = 0; i < values.length; i++) { | |
| 1268 if (i > 0) buf.add(', '); | |
| 1269 buf.add(values[i].code); | |
| 1270 } | |
| 1271 buf.add(']'); | |
| 1272 var listCode = buf.toString(); | |
| 1273 | |
| 1274 if (!isConst) return listCode; | |
| 1275 | |
| 1276 var v = new Value(world.listType, listCode, span); | |
| 1277 final immutableListCtor = world.immutableListType.getConstructor('from'); | |
| 1278 final result = immutableListCtor.invoke(world.gen.mainContext, null, | |
| 1279 new TypeValue(v.type, span), new Arguments(null, [v])); | |
| 1280 return result.code; | |
| 1281 } | |
| 1282 | |
| 1283 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1284 // TODO(jimhug): Support int/double better | |
| 1285 if (other is! ListValue) return super.binop(kind, other, context, node); | |
| 1286 | |
| 1287 switch (kind) { | |
| 1288 case TokenKind.EQ_STRICT: | |
| 1289 return new BoolValue(type == other.type && code == other.code, | |
| 1290 isConst && other.isConst, node.span); | |
| 1291 case TokenKind.NE_STRICT: | |
| 1292 return new BoolValue(type != other.type || code != other.code, | |
| 1293 isConst && other.isConst, node.span); | |
| 1294 } | |
| 1295 | |
| 1296 return super.binop(kind, other, context, node); | |
| 1297 } | |
| 1298 | |
| 1299 GlobalValue getGlobalValue() { | |
| 1300 assert(isConst); | |
| 1301 | |
| 1302 return world.gen.globalForConst(this, values); | |
| 1303 } | |
| 1304 } | |
| 1305 | |
| 1306 | |
| 1307 class MapValue extends EvaluatedValue { | |
| 1308 final List<Value> values; | |
| 1309 | |
| 1310 MapValue(this.values, bool isConst, Type type, SourceSpan span): | |
| 1311 super(isConst, type, span); | |
| 1312 | |
| 1313 String get code() { | |
| 1314 // Cache? | |
| 1315 var items = new ListValue(values, false, world.listType, span); | |
| 1316 var tp = world.coreimpl.topType; | |
| 1317 Member f = isConst ? tp.getMember('_constMap') : tp.getMember('_map'); | |
| 1318 // TODO(jimhug): Clean up invoke signature | |
| 1319 var value = f.invoke(world.gen.mainContext, null, new TypeValue(tp, null), | |
| 1320 new Arguments(null, [items])); | |
| 1321 return value.code; | |
| 1322 } | |
| 1323 | |
| 1324 GlobalValue getGlobalValue() { | |
| 1325 assert(isConst); | |
| 1326 | |
| 1327 return world.gen.globalForConst(this, values); | |
| 1328 } | |
| 1329 | |
| 1330 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1331 if (other is! MapValue) return super.binop(kind, other, context, node); | |
| 1332 | |
| 1333 switch (kind) { | |
| 1334 case TokenKind.EQ_STRICT: | |
| 1335 return new BoolValue(type == other.type && code == other.code, | |
| 1336 isConst && other.isConst, node.span); | |
| 1337 case TokenKind.NE_STRICT: | |
| 1338 return new BoolValue(type != other.type || code != other.code, | |
| 1339 isConst && other.isConst, node.span); | |
| 1340 } | |
| 1341 | |
| 1342 return super.binop(kind, other, context, node); | |
| 1343 } | |
| 1344 } | |
| 1345 | |
| 1346 | |
| 1347 class ObjectValue extends EvaluatedValue { | |
| 1348 final Map<FieldMember, Value> fields; | |
| 1349 final List<FieldMember> fieldsInInitOrder; | |
| 1350 bool seenNativeInitializer = false; | |
| 1351 | |
| 1352 String _code; | |
| 1353 | |
| 1354 ObjectValue(bool isConst, Type type, SourceSpan span) | |
| 1355 : fields = new Map<FieldMember, Value>(), | |
| 1356 fieldsInInitOrder = <FieldMember>[], | |
| 1357 super(isConst, type, span); | |
| 1358 | |
| 1359 String get code() { | |
| 1360 if (_code === null) validateInitialized(null); | |
| 1361 return _code; | |
| 1362 } | |
| 1363 | |
| 1364 initFields() { | |
| 1365 var allMembers = world.gen._orderValues(type.genericType.getAllMembers()); | |
| 1366 for (var f in allMembers) { | |
| 1367 if (f.isField && !f.isStatic && f.declaringType.isClass) { | |
| 1368 _replaceField(f, f.computeValue(), true); | |
| 1369 } | |
| 1370 } | |
| 1371 } | |
| 1372 | |
| 1373 setField(Member field, Value value, [bool duringInit = false]) { | |
| 1374 // Unpack constant values | |
| 1375 if (value.isConst && value is VariableValue) { | |
| 1376 value = value.dynamic.value; | |
| 1377 } | |
| 1378 var currentValue = fields[field]; | |
| 1379 if (isConst && !value.isConst) { | |
| 1380 world.error('used of non-const value in const intializer', value.span); | |
| 1381 } | |
| 1382 | |
| 1383 if (currentValue === null) { | |
| 1384 _replaceField(field, value, duringInit); | |
| 1385 if (field.isFinal && !duringInit) { | |
| 1386 world.error('cannot initialize final fields outside of initializer', | |
| 1387 value.span); | |
| 1388 } | |
| 1389 } else { | |
| 1390 // TODO(jimhug): Clarify spec on reinitializing fields with defaults. | |
| 1391 if (field.isFinal && field.computeValue() === null) { | |
| 1392 world.error('reassignment of field not allowed', value.span, | |
| 1393 field.span); | |
| 1394 } else { | |
| 1395 _replaceField(field, value, duringInit); | |
| 1396 } | |
| 1397 } | |
| 1398 } | |
| 1399 | |
| 1400 _replaceField(Member field, Value value, bool duringInit) { | |
| 1401 if (duringInit) { | |
| 1402 for (int i = 0; i < fieldsInInitOrder.length; i++) { | |
| 1403 if (fieldsInInitOrder[i] == field) { | |
| 1404 fieldsInInitOrder[i] = null; | |
| 1405 break; | |
| 1406 } | |
| 1407 } | |
| 1408 // TODO(sra): What if the overridden value contains an effect? | |
| 1409 fieldsInInitOrder.add(field); | |
| 1410 } | |
| 1411 fields[field] = value; //currentValue.union(value); | |
| 1412 } | |
| 1413 | |
| 1414 validateInitialized(SourceSpan span) { | |
| 1415 var buf = new StringBuffer(); | |
| 1416 buf.add('Object.create('); | |
| 1417 buf.add('${type.jsname}.prototype, '); | |
| 1418 | |
| 1419 buf.add('{'); | |
| 1420 bool addComma = false; | |
| 1421 for (var field in fields.getKeys()) { | |
| 1422 if (addComma) buf.add(', '); | |
| 1423 buf.add(field.jsname); | |
| 1424 buf.add(': '); | |
| 1425 buf.add('{"value": '); | |
| 1426 if (fields[field] === null) { | |
| 1427 world.error("Required field '${field.name}' was not initialized", | |
| 1428 span, field.span); | |
| 1429 buf.add('null'); | |
| 1430 } else { | |
| 1431 buf.add(fields[field].code); | |
| 1432 } | |
| 1433 buf.add(', writeable: false}'); | |
| 1434 addComma = true; | |
| 1435 } | |
| 1436 buf.add('})'); | |
| 1437 _code = buf.toString(); | |
| 1438 } | |
| 1439 | |
| 1440 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1441 if (other is! ObjectValue) return super.binop(kind, other, context, node); | |
| 1442 | |
| 1443 switch (kind) { | |
| 1444 case TokenKind.EQ_STRICT: | |
| 1445 case TokenKind.EQ: | |
| 1446 return new BoolValue(type == other.type && code == other.code, | |
| 1447 isConst && other.isConst, node.span); | |
| 1448 case TokenKind.NE_STRICT: | |
| 1449 case TokenKind.NE: | |
| 1450 return new BoolValue(type != other.type || code != other.code, | |
| 1451 isConst && other.isConst, node.span); | |
| 1452 } | |
| 1453 | |
| 1454 return super.binop(kind, other, context, node); | |
| 1455 } | |
| 1456 | |
| 1457 } | |
| 1458 | |
| 1459 | |
| 1460 /** | |
| 1461 * A global value in the generated code, which corresponds to either a static | |
| 1462 * field or a memoized const expressions. | |
| 1463 */ | |
| 1464 class GlobalValue extends Value implements Comparable { | |
| 1465 /** Static field definition (null for constant exp). */ | |
| 1466 final FieldMember field; | |
| 1467 | |
| 1468 /** | |
| 1469 * When [this] represents a constant expression, the global variable name | |
| 1470 * generated for it. | |
| 1471 */ | |
| 1472 final String name; | |
| 1473 | |
| 1474 /** The value of the field or constant expression to declare. */ | |
| 1475 final Value exp; | |
| 1476 | |
| 1477 /** True for either cont expressions or a final static field. */ | |
| 1478 final bool isConst; | |
| 1479 | |
| 1480 /** The actual constant value, when [isConst] is true. */ | |
| 1481 get actualValue() => exp.dynamic.actualValue; | |
| 1482 | |
| 1483 /** If [isConst], the [EvaluatedValue] that defines this value. */ | |
| 1484 EvaluatedValue get constValue() => isConst ? exp.constValue : null; | |
| 1485 | |
| 1486 /** Other globals that should be defined before this global. */ | |
| 1487 final List<GlobalValue> dependencies; | |
| 1488 | |
| 1489 GlobalValue(Type type, String code, bool isConst, | |
| 1490 this.field, this.name, this.exp, | |
| 1491 SourceSpan span, List<Value> deps): | |
| 1492 isConst = isConst, | |
| 1493 dependencies = <GlobalValue>[], | |
| 1494 super(type, code, span) { | |
| 1495 | |
| 1496 // store transitive-dependencies so sorting algorithm works correctly. | |
| 1497 for (var dep in deps) { | |
| 1498 if (dep is GlobalValue) { | |
| 1499 dependencies.add(dep); | |
| 1500 dependencies.addAll(dep.dependencies); | |
| 1501 } | |
| 1502 } | |
| 1503 } | |
| 1504 | |
| 1505 bool get needsTemp() => !isConst; | |
| 1506 | |
| 1507 int compareTo(GlobalValue other) { | |
| 1508 // order by dependencies, o.w. by name | |
| 1509 if (other == this) { | |
| 1510 return 0; | |
| 1511 } else if (dependencies.indexOf(other) >= 0) { | |
| 1512 return 1; | |
| 1513 } else if (other.dependencies.indexOf(this) >= 0) { | |
| 1514 return -1; | |
| 1515 } else if (dependencies.length > other.dependencies.length) { | |
| 1516 return 1; | |
| 1517 } else if (dependencies.length < other.dependencies.length) { | |
| 1518 return -1; | |
| 1519 } else if (name == null && other.name != null) { | |
| 1520 return 1; | |
| 1521 } else if (name != null && other.name == null) { | |
| 1522 return -1; | |
| 1523 } else if (name != null) { | |
| 1524 return name.compareTo(other.name); | |
| 1525 } else { | |
| 1526 return field.name.compareTo(other.field.name); | |
| 1527 } | |
| 1528 } | |
| 1529 } | |
| 1530 | |
| 1531 /** | |
| 1532 * Represents the hidden or implicit value in a bare reference like 'a'. | |
| 1533 * This could be this, the current type, or the current library for purposes | |
| 1534 * of resolving members. | |
| 1535 */ | |
| 1536 class BareValue extends Value { | |
| 1537 final bool isType; | |
| 1538 final CallingContext home; | |
| 1539 | |
| 1540 String _code; | |
| 1541 | |
| 1542 BareValue(this.home, CallingContext outermost, SourceSpan span): | |
| 1543 isType = outermost.isStatic, | |
| 1544 super(outermost.method.declaringType, null, span); | |
| 1545 | |
| 1546 bool get needsTemp() => false; | |
| 1547 bool _shouldBindDynamically() => false; | |
| 1548 | |
| 1549 String get code() => _code; | |
| 1550 | |
| 1551 // TODO(jimhug): Lazy initialization here is weird! | |
| 1552 void _ensureCode() { | |
| 1553 if (_code === null) _code = isType ? type.jsname : home._makeThisCode(); | |
| 1554 } | |
| 1555 | |
| 1556 MemberSet _tryResolveMember(CallingContext context, String name, Node node) { | |
| 1557 assert(context == home); | |
| 1558 | |
| 1559 // TODO(jimhug): Confirm this matches final resolution of issue 641. | |
| 1560 var member = type.getMember(name); | |
| 1561 if (member == null || member.declaringType != type) { | |
| 1562 var libMember = home.library.lookup(name, span); | |
| 1563 if (libMember !== null) { | |
| 1564 return libMember.preciseMemberSet; | |
| 1565 } | |
| 1566 } | |
| 1567 | |
| 1568 _ensureCode(); | |
| 1569 return super._tryResolveMember(context, name, node); | |
| 1570 } | |
| 1571 } | |
| 1572 | |
| 1573 /** A reference to 'super'. */ | |
| 1574 // TODO(jmesserly): override resolveMember to clean up the one on Value | |
| 1575 class SuperValue extends Value { | |
| 1576 SuperValue(Type parentType, SourceSpan span): | |
| 1577 super(parentType, 'this', span); | |
| 1578 | |
| 1579 bool get needsTemp() => false; | |
| 1580 bool get isSuper() => true; | |
| 1581 bool _shouldBindDynamically() => false; | |
| 1582 | |
| 1583 Value _tryUnion(Value right) => right is SuperValue ? this : null; | |
| 1584 } | |
| 1585 | |
| 1586 /** A reference to 'this'. */ | |
| 1587 class ThisValue extends Value { | |
| 1588 ThisValue(Type type, String code, SourceSpan span): | |
| 1589 super(type, code, span); | |
| 1590 | |
| 1591 bool get needsTemp() => false; | |
| 1592 bool _shouldBindDynamically() => false; | |
| 1593 | |
| 1594 Value _tryUnion(Value right) => right is ThisValue ? this : null; | |
| 1595 } | |
| 1596 | |
| 1597 /** A pretend first-class type. */ | |
| 1598 class TypeValue extends Value { | |
| 1599 TypeValue(Type type, SourceSpan span): | |
| 1600 super(type, null, span); | |
| 1601 | |
| 1602 bool get needsTemp() => false; | |
| 1603 bool get isType() => true; | |
| 1604 bool _shouldBindDynamically() => false; | |
| 1605 | |
| 1606 Value _tryUnion(Value right) => right is TypeValue ? this : null; | |
| 1607 } | |
| 1608 | |
| 1609 | |
| 1610 /** | |
| 1611 * A value that represents a variable or parameter. The [assigned] value can be | |
| 1612 * mutated when the variable is assigned to a new Value. | |
| 1613 */ | |
| 1614 class VariableValue extends Value { | |
| 1615 final bool isFinal; | |
| 1616 final Value value; | |
| 1617 | |
| 1618 VariableValue(Type staticType, String code, SourceSpan span, | |
| 1619 [this.isFinal=false, Value value]): | |
| 1620 value = _unwrap(value), | |
| 1621 super(staticType, code, span) { | |
| 1622 | |
| 1623 // these are not really first class | |
| 1624 assert(value === null || !value.isType && !value.isSuper); | |
| 1625 | |
| 1626 // TODO(jmesserly): should we do convertTo here, so the check doesn't get | |
| 1627 // missed? There are some cases where this assert doesn't hold. | |
| 1628 // assert(value === null || value.staticType == staticType); | |
| 1629 } | |
| 1630 | |
| 1631 static Value _unwrap(Value v) { | |
| 1632 if (v === null) return null; | |
| 1633 if (v is VariableValue) { | |
| 1634 v = v.dynamic.value; | |
| 1635 } | |
| 1636 return v; | |
| 1637 } | |
| 1638 | |
| 1639 Value _tryUnion(Value right) => Value.union(value, right); | |
| 1640 | |
| 1641 bool get needsTemp() => false; | |
| 1642 Type get type() => value !== null ? value.type : staticType; | |
| 1643 Type get staticType() => super.type; | |
| 1644 bool get isConst() => value !== null ? value.isConst : false; | |
| 1645 | |
| 1646 // TODO(jmesserly): we could use this for checking uninitialized values | |
| 1647 bool get isInitialized() => value != null; | |
| 1648 | |
| 1649 VariableValue replaceValue(Value v) => | |
| 1650 new VariableValue(staticType, code, span, isFinal, v); | |
| 1651 | |
| 1652 // TODO(jmesserly): anything else to override? | |
| 1653 Value unop(int kind, CallingContext context, var node) { | |
| 1654 if (value != null) { | |
| 1655 return replaceValue(value.unop(kind, context, node)); | |
| 1656 } | |
| 1657 return super.unop(kind, context, node); | |
| 1658 } | |
| 1659 Value binop(int kind, var other, CallingContext context, var node) { | |
| 1660 if (value != null) { | |
| 1661 return replaceValue(value.binop(kind, _unwrap(other), context, node)); | |
| 1662 } | |
| 1663 return super.binop(kind, other, context, node); | |
| 1664 } | |
| 1665 } | |
| OLD | NEW |