|
6 | 6 | import {
|
7 | 7 | BuiltinNames,
|
8 | 8 | compileCall as compileBuiltinCall,
|
9 |
| - compileAbort, |
10 | 9 | compileVisitGlobals,
|
11 | 10 | compileVisitMembers,
|
12 | 11 | compileRTTI,
|
@@ -2499,7 +2498,9 @@ export class Compiler extends DiagnosticEmitter {
|
2499 | 2498 | let newArgs = (<NewExpression>value).arguments;
|
2500 | 2499 | if (newArgs.length) message = newArgs[0]; // FIXME: naively assumes type string
|
2501 | 2500 | }
|
2502 |
| - stmts.push(compileAbort(this, message, statement)); |
| 2501 | + stmts.push( |
| 2502 | + this.makeAbort(message, statement) |
| 2503 | + ); |
2503 | 2504 |
|
2504 | 2505 | return this.module.flatten(stmts);
|
2505 | 2506 | }
|
@@ -3125,21 +3126,48 @@ export class Compiler extends DiagnosticEmitter {
|
3125 | 3126 | // any to void
|
3126 | 3127 | if (toType.kind == TypeKind.VOID) return module.drop(expr);
|
3127 | 3128 |
|
3128 |
| - if (this.currentFlow.isNonnull(expr, fromType)) fromType = fromType.nonNullableType; |
| 3129 | + // reference involved |
| 3130 | + if (fromType.is(TypeFlags.REFERENCE) || toType.is(TypeFlags.REFERENCE)) { |
| 3131 | + if (this.currentFlow.isNonnull(expr, fromType)) { |
| 3132 | + fromType = fromType.nonNullableType; |
| 3133 | + } else if (explicit && fromType.is(TypeFlags.NULLABLE) && !toType.is(TypeFlags.NULLABLE)) { |
| 3134 | + // explicit conversion from nullable to non-nullable requires a runtime |
| 3135 | + // check here because nonnull state above already didn't know better |
| 3136 | + if (!this.options.noAssert) { |
| 3137 | + expr = this.makeRuntimeNonNullCheck(expr, fromType, reportNode); |
| 3138 | + } |
| 3139 | + fromType = fromType.nonNullableType; |
| 3140 | + } |
| 3141 | + if (fromType.isAssignableTo(toType)) { // downcast or same |
| 3142 | + assert(fromType.kind == toType.kind); |
| 3143 | + this.currentType = toType; |
| 3144 | + return expr; |
| 3145 | + } |
| 3146 | + if (explicit && toType.nonNullableType.isAssignableTo(fromType)) { // upcast |
| 3147 | + // <Cat | null>(<Animal>maybeCat) |
| 3148 | + assert(fromType.kind == toType.kind); |
| 3149 | + if (!this.options.noAssert) { |
| 3150 | + expr = this.makeRuntimeUpcastCheck(expr, fromType, toType, reportNode); |
| 3151 | + } |
| 3152 | + this.currentType = toType; |
| 3153 | + return expr; |
| 3154 | + } |
| 3155 | + this.error( |
| 3156 | + DiagnosticCode.Type_0_is_not_assignable_to_type_1, |
| 3157 | + reportNode.range, fromType.toString(), toType.toString() |
| 3158 | + ); |
| 3159 | + this.currentType = toType; |
| 3160 | + return module.unreachable(); |
| 3161 | + } |
| 3162 | + |
| 3163 | + // not dealing with references from here on |
3129 | 3164 |
|
3130 | 3165 | if (!fromType.isAssignableTo(toType)) {
|
3131 | 3166 | if (!explicit) {
|
3132 |
| - if (fromType.nonNullableType == toType) { |
3133 |
| - this.error( |
3134 |
| - DiagnosticCode.Object_is_possibly_null, |
3135 |
| - reportNode.range |
3136 |
| - ); // recoverable |
3137 |
| - } else { |
3138 |
| - this.error( |
3139 |
| - DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast, |
3140 |
| - reportNode.range, fromType.toString(), toType.toString() |
3141 |
| - ); // recoverable |
3142 |
| - } |
| 3167 | + this.error( |
| 3168 | + DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast, |
| 3169 | + reportNode.range, fromType.toString(), toType.toString() |
| 3170 | + ); // recoverable |
3143 | 3171 | }
|
3144 | 3172 | }
|
3145 | 3173 |
|
@@ -3319,19 +3347,9 @@ export class Compiler extends DiagnosticEmitter {
|
3319 | 3347 | expression.expression.range
|
3320 | 3348 | );
|
3321 | 3349 | } else if (!this.options.noAssert) {
|
3322 |
| - let module = this.module; |
3323 |
| - let flow = this.currentFlow; |
3324 |
| - let temp = flow.getTempLocal(type); |
3325 |
| - if (!flow.canOverflow(expr, type)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); |
3326 |
| - flow.setLocalFlag(temp.index, LocalFlags.NONNULL); |
3327 |
| - expr = module.if( |
3328 |
| - module.local_tee(temp.index, expr), |
3329 |
| - module.local_get(temp.index, type.toNativeType()), |
3330 |
| - module.unreachable() |
3331 |
| - ); |
3332 |
| - flow.freeTempLocal(temp); |
| 3350 | + expr = this.makeRuntimeNonNullCheck(expr, type, expression); |
3333 | 3351 | }
|
3334 |
| - this.currentType = this.currentType.nonNullableType; |
| 3352 | + this.currentType = type.nonNullableType; |
3335 | 3353 | return expr;
|
3336 | 3354 | }
|
3337 | 3355 | default: assert(false);
|
@@ -9376,6 +9394,98 @@ export class Compiler extends DiagnosticEmitter {
|
9376 | 9394 | }
|
9377 | 9395 | return stmts;
|
9378 | 9396 | }
|
| 9397 | + |
| 9398 | + /** Makes a call to `abort`, if present, otherwise creates a trap. */ |
| 9399 | + makeAbort( |
| 9400 | + /** Message argument of type string, if any. */ |
| 9401 | + message: Expression | null, |
| 9402 | + /** Code location to report when aborting. */ |
| 9403 | + codeLocation: Node |
| 9404 | + ): ExpressionRef { |
| 9405 | + var program = this.program; |
| 9406 | + var module = this.module; |
| 9407 | + var stringInstance = program.stringInstance; |
| 9408 | + var abortInstance = program.abortInstance; |
| 9409 | + if (!abortInstance || !this.compileFunction(abortInstance)) return module.unreachable(); |
| 9410 | + |
| 9411 | + var messageArg: ExpressionRef; |
| 9412 | + if (message !== null) { |
| 9413 | + // The message argument works much like an arm of an IF that does not become executed if the |
| 9414 | + // assertion succeeds respectively is only being computed if the program actually crashes. |
| 9415 | + // Hence, let's make it so that the autorelease is skipped at the end of the current block, |
| 9416 | + // essentially ignoring the message GC-wise. Doesn't matter anyway on a crash. |
| 9417 | + messageArg = this.compileExpression(message, stringInstance.type, Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN); |
| 9418 | + } else { |
| 9419 | + messageArg = this.makeZero(stringInstance.type); |
| 9420 | + } |
| 9421 | + |
| 9422 | + var filenameArg = this.ensureStaticString(codeLocation.range.source.normalizedPath); |
| 9423 | + return module.block(null, [ |
| 9424 | + module.call( |
| 9425 | + abortInstance.internalName, [ |
| 9426 | + messageArg, |
| 9427 | + filenameArg, |
| 9428 | + module.i32(codeLocation.range.line), |
| 9429 | + module.i32(codeLocation.range.column) |
| 9430 | + ], |
| 9431 | + NativeType.None |
| 9432 | + ), |
| 9433 | + module.unreachable() |
| 9434 | + ]); |
| 9435 | + } |
| 9436 | + |
| 9437 | + /** Makes a runtime non-null check, e.g. on `<Type>possiblyNull` or `possiblyNull!`. */ |
| 9438 | + makeRuntimeNonNullCheck( |
| 9439 | + /** Expression being checked. */ |
| 9440 | + expr: ExpressionRef, |
| 9441 | + /** Type of the expression. */ |
| 9442 | + type: Type, |
| 9443 | + /** Report node. */ |
| 9444 | + reportNode: Node |
| 9445 | + ): ExpressionRef { |
| 9446 | + assert(type.is(TypeFlags.NULLABLE | TypeFlags.REFERENCE)); |
| 9447 | + var module = this.module; |
| 9448 | + var flow = this.currentFlow; |
| 9449 | + var temp = flow.getTempLocal(type); |
| 9450 | + if (!flow.canOverflow(expr, type)) flow.setLocalFlag(temp.index, LocalFlags.WRAPPED); |
| 9451 | + flow.setLocalFlag(temp.index, LocalFlags.NONNULL); |
| 9452 | + expr = module.if( |
| 9453 | + module.local_tee(temp.index, expr), |
| 9454 | + module.local_get(temp.index, type.toNativeType()), |
| 9455 | + this.makeAbort(null, reportNode) // TODO: throw |
| 9456 | + ); |
| 9457 | + flow.freeTempLocal(temp); |
| 9458 | + return expr; |
| 9459 | + } |
| 9460 | + |
| 9461 | + /** Makes a runtime upcast check, e.g. on `<Child>parent`. */ |
| 9462 | + makeRuntimeUpcastCheck( |
| 9463 | + /** Expression being upcast. */ |
| 9464 | + expr: ExpressionRef, |
| 9465 | + /** Type of the expression. */ |
| 9466 | + type: Type, |
| 9467 | + /** Type casting to. */ |
| 9468 | + toType: Type, |
| 9469 | + /** Report node. */ |
| 9470 | + reportNode: Node |
| 9471 | + ): ExpressionRef { |
| 9472 | + assert(toType.is(TypeFlags.REFERENCE) && toType.nonNullableType.isAssignableTo(type)); |
| 9473 | + var module = this.module; |
| 9474 | + var flow = this.currentFlow; |
| 9475 | + var temp = flow.getTempLocal(type); |
| 9476 | + var instanceofInstance = this.program.instanceofInstance; |
| 9477 | + assert(this.compileFunction(instanceofInstance)); |
| 9478 | + expr = module.if( |
| 9479 | + module.call(instanceofInstance.internalName, [ |
| 9480 | + module.local_tee(temp.index, expr), |
| 9481 | + module.i32(assert(toType.classReference).id) |
| 9482 | + ], NativeType.I32), |
| 9483 | + module.local_get(temp.index, type.toNativeType()), |
| 9484 | + this.makeAbort(null, reportNode) // TODO: throw |
| 9485 | + ); |
| 9486 | + flow.freeTempLocal(temp); |
| 9487 | + return expr; |
| 9488 | + } |
9379 | 9489 | }
|
9380 | 9490 |
|
9381 | 9491 | // helpers
|
|
0 commit comments