diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index 8c6957760..baf19af8f 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -124,10 +124,6 @@ #define DLL_IMPORT "dllimport" #define DLL_NAME "dllname" -// module attribute: the class types the module throws, for binding an untyped catch on the -// Itanium path (linux::SaveCatchVarOpLowering) -#define THROWN_CLASSES_ATTR_NAME "ts.thrown_classes" - #if __LP64__ #define TRAMPOLINE_SIZE 48 #else diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h index 2dae3ce2d..493f4a8d8 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h @@ -55,6 +55,14 @@ constexpr const auto *typeName = ""; constexpr const auto *classTypeInfoName = "_ZTVN10__cxxabiv117__class_type_infoE"; constexpr const auto *singleInheritanceClassTypeInfoName = "_ZTVN10__cxxabiv120__si_class_type_infoE"; constexpr const auto *pointerTypeInfoName = "_ZTVN10__cxxabiv119__pointer_type_infoE"; +// A class is thrown as a pointer, and its `_ZTIP` - a __pointer_type_info - has one +// more field after the four libstdc++ reads: the address of a thunk that boxes the thrown +// instance into an `any` (the class's own descriptor included). An untyped catch calls it for +// a class it knows nothing about, e.g. one only another module throws - the Itanium +// counterpart of the Windows `.PEAX` copy thunk. Every module emitting the type_info (a throw +// or a typed catch of the class) emits the same thunk, so the linkonce_odr copies agree. +constexpr const auto *boxThunkPrefix = ".eh.copy.box."; +constexpr int boxThunkField = 4; } // namespace ClassType } // namespace linux diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVC.h b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVC.h index 8cfd028b0..ea4673866 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVC.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVC.h @@ -24,10 +24,12 @@ class MLIRRTTIHelperVC { } - // Windows only: the Itanium path has no copy functions to point at a thunk + // Windows: the copy thunks of the CatchableType records; Itanium: the box thunk of a class's + // pointer type_info - see linux::ClassType::boxThunkPrefix void setCopyThunkBuilder(MLIRRTTIHelperVCWin32::CopyThunkBuilder builder) { rttiWin.setCopyThunkBuilder(builder); + rttiLinux.setCopyThunkBuilder(builder); } bool setRTTIForType(mlir::Location loc, mlir::Type type, std::function resolveClassInfo) diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h index e4a028003..6f73c4d2f 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h @@ -52,6 +52,17 @@ class MLIRRTTIHelperVCLinux SmallVector types; + public: + // Builds the thunk `name`: void name(ref dest, ref src) - the same builder as + // MLIRRTTIHelperVCWin32's. Here it makes a class's box thunk, see linux::ClassType::boxThunkPrefix. + using CopyThunkBuilder = std::function; + + private: + // the class a class type_info is emitted for: the box thunk's source + mlir::Type classType; + CopyThunkBuilder copyThunkBuilder; + public: MLIRRTTIHelperVCLinux(mlir::OpBuilder &rewriter, mlir::ModuleOp &parentModule, CompileOptions& compileOptions) : rewriter(rewriter), parentModule(parentModule), mth(rewriter.getContext(), compileOptions), mlh(), mcl(rewriter, compileOptions) @@ -59,6 +70,11 @@ class MLIRRTTIHelperVCLinux // setI32AsCatchType(); } + void setCopyThunkBuilder(CopyThunkBuilder builder) + { + copyThunkBuilder = builder; + } + void setF32AsCatchType() { types.push_back({linux::F32Type::typeName, TypeInfo::Value, -1}); @@ -191,6 +207,7 @@ class MLIRRTTIHelperVCLinux classInfo->getBasesWithRoot(classAndBases); setClassTypeAsCatchType(classAndBases); + this->classType = classInfo->classType; }) .Case([&](auto anyType) { // This overload only declares the RTTI globals a catch may reference. An @@ -205,6 +222,9 @@ class MLIRRTTIHelperVCLinux setI64AsCatchType(); setStringTypeAsCatchType(); setI8PtrAsCatchType(); + // what tells a thrown class apart from the rest: its type_info is a + // __pointer_type_info, which carries the box thunk + types.push_back({linux::ClassType::pointerTypeInfoName, TypeInfo::Value, -1}); }) .Default([&](auto type) { LLVM_DEBUG(llvm::dbgs() << "...unsupported throw/catch type: " << type << "\n";); @@ -428,7 +448,8 @@ class MLIRRTTIHelperVCLinux case TypeInfo::SingleInheritance_ClassTypeInfo: return mth.getTupleType({mth.getOpaqueType(), mth.getOpaqueType(), mth.getOpaqueType()}); case TypeInfo::Pointer_TypeInfo: - return mth.getTupleType({mth.getOpaqueType(), mth.getOpaqueType(), mth.getI32Type(), mth.getOpaqueType()}); + // the last field is ours, not libstdc++'s - see linux::ClassType::boxThunkPrefix + return mth.getTupleType({mth.getOpaqueType(), mth.getOpaqueType(), mth.getI32Type(), mth.getOpaqueType(), mth.getOpaqueType()}); default: return mth.getTupleType({mth.getOpaqueType(), mth.getOpaqueType()}); } @@ -490,13 +511,7 @@ class MLIRRTTIHelperVCLinux mlir::Type tiType; if (classType) { - SmallVector tiTypes; - tiTypes.push_back(mth.getOpaqueType()); - tiTypes.push_back(mth.getOpaqueType()); - tiTypes.push_back(mth.getI32Type()); - tiTypes.push_back(mth.getOpaqueType()); - - tiType = mth.getTupleType(tiTypes); + tiType = getTIType(TypeInfo::Pointer_TypeInfo); } else { @@ -508,6 +523,31 @@ class MLIRRTTIHelperVCLinux return throwInfoPtr; } + // The box thunk field of a class's pointer type_info: the thunk's address, or null when + // nothing here can build it - an untyped catch then boxes the instance as a plain object. + mlir::Value boxThunkValue(mlir::Location loc, StringRef label) + { + auto thunkName = std::string(linux::ClassType::boxThunkPrefix) + label.str(); + if (!parentModule.lookupSymbol(thunkName)) + { + auto built = false; + if (copyThunkBuilder && classType) + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + built = mlir::succeeded(copyThunkBuilder(loc, thunkName, classType, mlir_ts::AnyType::get(rewriter.getContext()))); + } + + if (!built) + { + auto nullValue = rewriter.create(loc, mth.getNullType()); + return rewriter.create(loc, mth.getOpaqueType(), nullValue); + } + } + + return rewriter.create(loc, mth.getOpaqueType(), + mlir::FlatSymbolRefAttr::get(rewriter.getContext(), thunkName)); + } + mlir::LogicalResult typeInfoRef(mlir::Location loc, StringRef className, TypeInfo ti, StringRef baseName = "", TypeInfo baseti = TypeInfo::Value) { @@ -551,6 +591,8 @@ class MLIRRTTIHelperVCLinux auto castValue4 = rewriter.create(loc, mth.getOpaqueType(), itemValue4); setStructValue(loc, structVal, castValue4, 3); + + setStructValue(loc, structVal, boxThunkValue(loc, labelValue(className, ti)), linux::ClassType::boxThunkField); } else if (ti == TypeInfo::SingleInheritance_ClassTypeInfo) { diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 183e00fed..30a50493a 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -1579,6 +1579,12 @@ struct FuncOpLowering : public TsLlvmPattern continue; } + // module-local: a private symbol alone still comes out external + if (name == "internal_linkage") { + newFuncOp->setAttr("llvm.linkage", LLVM::LinkageAttr::get(getContext(), LLVM::linkage::Linkage::Internal)); + continue; + } + auto addAttr = std::find(skipAttrs.begin(), skipAttrs.end(), name) == skipAttrs.end(); if (addAttr && name == DLL_NAME) @@ -5138,26 +5144,53 @@ struct SaveCatchVarOpLowering : public TsLlvmPattern {::typescript::linux::I8PtrType::typeName, mlir_ts::AnyType::get(ctx)}}; // A class needs its own type descriptor in the box - unboxing checks for a class tag, - // and the descriptor carries the class's release routine - so only the classes this - // module throws can be recognised (MLIRGen lists them, see recordThrownClass). A class - // thrown from another module falls through to the plain-object box below. - if (auto thrownClasses = saveCatchVarOp->getParentOfType()->getAttrOfType( - THROWN_CLASSES_ATTR_NAME)) - { - for (auto thrownClass : thrownClasses.getAsValueRange()) - { - auto classType = cast(thrownClass); - auto name = classType.getName().getValue(); - std::string typeInfoName = "_ZTIP" + std::to_string(name.size()) + name.str(); - candidates.push_back({typeInfoName, classType}); - } - } + // and the descriptor carries the class's release routine - and the class may be one this + // module has never heard of, thrown from another. So the thrower supplies the box: a + // class is thrown as a pointer, and its __pointer_type_info carries a thunk that boxes + // the instance (see linux::ClassType::boxThunkPrefix). Anything else - a foreign C++ + // exception - is boxed as a plain object. + auto boxClass = [&]() -> mlir::Value { + auto pointerTypeInfoVtable = rewriter.create( + loc, ptrTy, ptrTy, + rewriter.create(loc, ptrTy, ::typescript::linux::ClassType::pointerTypeInfoName), + ArrayRef{2}); + auto typeInfoVtable = rewriter.create(loc, ptrTy, typeInfo); + auto isPointer = rewriter.create(loc, LLVM::ICmpPredicate::eq, typeInfoVtable, pointerTypeInfoVtable); + return clh.conditionalExpressionLowering( + loc, ptrTy, isPointer, + [&](OpBuilder &, Location) -> mlir::Value { + auto pointerTypeInfoType = LLVM::LLVMStructType::getLiteral( + ctx, {ptrTy, ptrTy, th.getI32Type(), ptrTy, ptrTy}, false); + auto thunkAddress = rewriter.create( + loc, ptrTy, pointerTypeInfoType, typeInfo, + ArrayRef{0, ::typescript::linux::ClassType::boxThunkField}); + auto thunk = rewriter.create(loc, ptrTy, thunkAddress); + auto hasThunk = rewriter.create(loc, LLVM::ICmpPredicate::ne, thunk, + rewriter.create(loc, ptrTy)); + return clh.conditionalExpressionLowering( + loc, ptrTy, hasThunk, + [&](OpBuilder &, Location) -> mlir::Value { + auto anyType = mlir_ts::AnyType::get(ctx); + auto boxSlot = rewriter.create( + loc, mlir_ts::RefType::get(anyType), mlir::Value(), rewriter.getBoolAttr(false), + rewriter.getIndexAttr(0)); + auto boxSlotPtr = rewriter.create(loc, ptrTy, boxSlot); + // void thunk(ref dest, ref src); exceptionInfo is the + // address of the thrown pointer + auto thunkType = LLVM::LLVMFunctionType::get(th.getVoidType(), {ptrTy, ptrTy}); + rewriter.create(loc, thunkType, ValueRange{thunk, boxSlotPtr, exceptionInfo}); + auto boxed = rewriter.create(loc, anyType, boxSlot); + return rewriter.create(loc, ptrTy, boxed); + }, + [&](OpBuilder &, Location) { return boxThrown(mlir_ts::OpaqueType::get(ctx)); }); + }, + [&](OpBuilder &, Location) { return boxThrown(mlir_ts::OpaqueType::get(ctx)); }); + }; std::function dispatch = [&](size_t index) -> mlir::Value { if (index == candidates.size()) { - // not a type tslang throws from this module; box the pointer as a plain object - return boxThrown(mlir_ts::OpaqueType::get(ctx)); + return boxClass(); } auto [typeInfoName, type] = candidates[index]; diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index d5b399ee5..98703215e 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2999,7 +2999,6 @@ class MLIRGenImpl void setCatchCopyThunkBuilder(MLIRRTTIHelperVC &rtti); - void recordThrownClass(mlir::Type thrownType); ValueOrLogicalResult mlirGen(TypeAssertion typeAssertionAST, const GenContext &genContext); @@ -3896,8 +3895,13 @@ class MLIRGenImpl if (instanceOf->left == SyntaxKind::Identifier) { NodeFactory nf(NodeFactoryFlags::None); - return addSafeCastStatement(instanceOf->left, nf.createTypeReferenceNode(instanceOf->right), - false, elseSafeCase, genContext); + // a type reference names its type with an entity name: `N.C` has to become a + // qualified name - handed over as the property access it is, it crashed + if (auto typeName = instanceOfEntityName(nf, instanceOf->right)) + { + return addSafeCastStatement(instanceOf->left, nf.createTypeReferenceNode(typeName), + false, elseSafeCase, genContext); + } } } } @@ -3920,6 +3924,30 @@ class MLIRGenImpl return mlir::success(); } + // the right side of `x instanceof C` as the entity name of a type reference: `C`, or `N.C` as + // a qualified name; anything else (a call, an element access) names no type to narrow to + EntityName instanceOfEntityName(NodeFactory &nf, Expression expr) + { + if (expr == SyntaxKind::Identifier) + { + return expr; + } + + if (expr == SyntaxKind::PropertyAccessExpression) + { + auto propertyAccess = expr.as(); + if (propertyAccess->name == SyntaxKind::Identifier) + { + if (auto left = instanceOfEntityName(nf, propertyAccess->expression)) + { + return nf.createQualifiedName(left, propertyAccess->name.as()); + } + } + } + + return undefined; + } + mlir::LogicalResult mlirGen(IfStatement ifStatementAST, const GenContext &genContext); mlir::LogicalResult mlirGen(DoStatement doStatementAST, const GenContext &genContext); @@ -4774,6 +4802,11 @@ class MLIRGenImpl return mlir::Value(); } + // A class imported from a shared library (-shared) keeps its rtti behind a pointer + // resolved at load time, and the property access already reads through it - so the + // value is a string here too. This used to insist on a ref for such a class and fail, + // which dropped the call: `x instanceof C` did not compile and `anyValue` always + // threw "Can't cast from any type". auto rttiOfClassValue = V(resultRtti); if (classInfo->isDynamicImport) { @@ -4781,18 +4814,13 @@ class MLIRGenImpl { rttiOfClassValue = builder.create(location, valueRefType.getElementType(), rttiOfClassValue); } - else - { - // isDynamicImport (@dllimport(path) with an argument) is itself an untested - // feature path with zero coverage anywhere in the test suite (see ยง4.10 of - // docs/not-implemented-audit.md) - this crash is nested one level deeper - // inside that already-untested path. Fail cleanly instead of crashing. - emitError(location, "unsupported RTTI value type for dynamic import"); - return mlir::Value(); - } } - assert(rttiOfClassValue); + if (!isa(rttiOfClassValue.getType())) + { + emitError(location, "unsupported RTTI value type ") << to_print(rttiOfClassValue.getType()); + return mlir::Value(); + } auto instanceOfFuncType = mlir_ts::FunctionType::get( builder.getContext(), SmallVector{getOpaqueType(), getStringType()}, diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index 9413c8c28..cb5263dd5 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -1066,6 +1066,11 @@ namespace mlirgen // symbol table and wire into this thunk's exit. auto funcOp = mlir_ts::FuncOp::create(location, name, funcType); funcOp.setPrivate(); + // internal: every module that throws or catches the type builds the same thunk, and a + // catch of a class another module throws failed to link (duplicate symbol). Not + // linkonce_odr in a comdat like the records that point at it: under the Win64 JIT a + // catch through such a thunk crashed (access violation). + funcOp->setAttr("internal_linkage", builder.getUnitAttr()); // its own context: the thunk is a function of its own, and has to be emitted even when // the throw or catch that needs it is only being generated speculatively @@ -1101,33 +1106,6 @@ namespace mlirgen }); } - // An untyped catch on the Itanium path is a catch-all that finds out what it caught from the - // exception's type_info, and a class needs its own descriptor in the `any` box it binds - so - // the lowering needs to know which classes can arrive. See linux::SaveCatchVarOpLowering. - void MLIRGenImpl::recordThrownClass(mlir::Type thrownType) - { - auto classType = dyn_cast(mth.stripLiteralType(thrownType)); - if (!classType) - { - return; - } - - SmallVector thrownClasses; - if (auto existing = theModule->getAttrOfType(THROWN_CLASSES_ATTR_NAME)) - { - thrownClasses.append(existing.begin(), existing.end()); - } - - auto classTypeAttr = mlir::TypeAttr::get(classType); - if (llvm::is_contained(thrownClasses, classTypeAttr)) - { - return; - } - - thrownClasses.push_back(classTypeAttr); - theModule->setAttr(THROWN_CLASSES_ATTR_NAME, builder.getArrayAttr(thrownClasses)); - } - mlir::LogicalResult MLIRGenImpl::mlirGen(ThrowStatement throwStatementAST, const GenContext &genContext) { auto location = loc(throwStatementAST); @@ -1149,11 +1127,6 @@ namespace mlirgen emitError(location, "Not supported type in throw"); return mlir::failure(); } - - if (!compileOptions.isWindows) - { - recordThrownClass(exception.getType()); - } } return mlir::success(); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 7d7c14a50..1a1ed0875 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -394,6 +394,7 @@ tslang_add_test(NAME test-compile-00-catch-untyped-value COMMAND test-runner "${ tslang_add_test(NAME test-compile-00-throw-bool-bigint COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts") tslang_add_test(NAME test-compile-00-throw-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts") tslang_add_test(NAME test-compile-00-cast-to-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") +tslang_add_test(NAME test-compile-00-instanceof-namespace-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") tslang_add_test(NAME test-compile-00-using-nested-scopes COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") tslang_add_test(NAME test-compile-00-using-unwind-progress COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_progress.ts") tslang_add_test(NAME test-compile-00-break-continue-scope-exit COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00break_continue_scope_exit.ts") @@ -823,6 +824,7 @@ tslang_add_test(NAME test-jit-00-catch-untyped-value COMMAND test-runner -jit "$ tslang_add_test(NAME test-jit-00-throw-bool-bigint COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts") tslang_add_test(NAME test-jit-00-throw-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts") tslang_add_test(NAME test-jit-00-cast-to-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") +tslang_add_test(NAME test-jit-00-instanceof-namespace-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") tslang_add_test(NAME test-jit-00-using-nested-scopes COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") tslang_add_test(NAME test-jit-00-using-unwind-progress COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_progress.ts") tslang_add_test(NAME test-jit-00-break-continue-scope-exit COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00break_continue_scope_exit.ts") @@ -1141,6 +1143,7 @@ tslang_add_test(NAME test-compile-export-import-generators COMMAND test-runner " tslang_add_test(NAME test-compile-rc-export-import-owned-returns COMMAND test-runner -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-compile-none-export-import-owned-returns COMMAND test-runner -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-compile-export-import-class-extends COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-compile-export-import-throw-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-compile-export-import-class-extends-multilevel COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") tslang_add_test(NAME test-compile-export-import-class-extends-implements-diamond COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") tslang_add_test(NAME test-compile-export-import-class-abstract COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_abstract.ts") @@ -1225,6 +1228,7 @@ tslang_add_test(NAME test-compile-shared-export-import-const-locals COMMAND test tslang_add_test(NAME test-compile-shared-export-import-const-functions COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") tslang_add_test(NAME test-compile-shared-export-import-generators COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_generators.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_generators.ts") tslang_add_test(NAME test-compile-shared-export-import-class-extends COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-compile-shared-export-import-throw-class COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-compile-shared-export-import-class-extends-implements-diamond COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") tslang_add_test(NAME test-compile-shared-export-import-class-extends-multilevel COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") tslang_add_test(NAME test-compile-shared-export-import-class-abstract COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_abstract.ts") @@ -1311,6 +1315,7 @@ tslang_add_test(NAME test-jit-shared-export-import-const-locals COMMAND test-run tslang_add_test(NAME test-jit-shared-export-import-const-functions COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") tslang_add_test(NAME test-jit-shared-export-import-generators COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_generators.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_generators.ts") tslang_add_test(NAME test-jit-shared-export-import-class-extends COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-jit-shared-export-import-throw-class COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-jit-shared-export-import-class-extends-multilevel COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") tslang_add_test(NAME test-jit-shared-export-import-class-extends-implements-diamond COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") tslang_add_test(NAME test-jit-shared-export-import-class-abstract COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_abstract.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_abstract.ts") @@ -1611,6 +1616,7 @@ set(TSLANG_CORPUS 00in.ts 00infer.ts 00instanceof.ts + 00instanceof_namespace_narrowing.ts 00interface_captures.ts 00interface_conjunction.ts 00interface_function_typed_field.ts @@ -2248,7 +2254,9 @@ tslang_add_test(NAME test-compile-none-shared-export-import-object-literal-with- tslang_add_test(NAME test-compile-rc-shared-export-import-owned-returns COMMAND test-runner -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-compile-none-shared-export-import-owned-returns COMMAND test-runner -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-compile-rc-shared-export-import-class-extends COMMAND test-runner -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-compile-rc-shared-export-import-throw-class COMMAND test-runner -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-compile-none-shared-export-import-class-extends COMMAND test-runner -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-compile-none-shared-export-import-throw-class COMMAND test-runner -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-compile-rc-shared-export-import-class-extends-implements-diamond COMMAND test-runner -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") tslang_add_test(NAME test-compile-none-shared-export-import-class-extends-implements-diamond COMMAND test-runner -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") tslang_add_test(NAME test-compile-rc-shared-export-import-class-extends-multilevel COMMAND test-runner -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") @@ -2332,7 +2340,9 @@ tslang_add_test(NAME test-jit-none-shared-export-import-object-literal-with-clas tslang_add_test(NAME test-jit-rc-shared-export-import-owned-returns COMMAND test-runner -jit -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-jit-none-shared-export-import-owned-returns COMMAND test-runner -jit -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") tslang_add_test(NAME test-jit-rc-shared-export-import-class-extends COMMAND test-runner -jit -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-jit-rc-shared-export-import-throw-class COMMAND test-runner -jit -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-jit-none-shared-export-import-class-extends COMMAND test-runner -jit -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") +tslang_add_test(NAME test-jit-none-shared-export-import-throw-class COMMAND test-runner -jit -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_throw_class.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_throw_class.ts") tslang_add_test(NAME test-jit-rc-shared-export-import-class-extends-multilevel COMMAND test-runner -jit -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") tslang_add_test(NAME test-jit-none-shared-export-import-class-extends-multilevel COMMAND test-runner -jit -shared -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") tslang_add_test(NAME test-jit-rc-shared-export-import-class-extends-implements-diamond COMMAND test-runner -jit -shared -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") diff --git a/tslang/test/tester/tests/00instanceof_namespace_narrowing.ts b/tslang/test/tester/tests/00instanceof_namespace_narrowing.ts new file mode 100644 index 000000000..87d3c69de --- /dev/null +++ b/tslang/test/tester/tests/00instanceof_namespace_narrowing.ts @@ -0,0 +1,42 @@ +// `if (x instanceof N.C)` narrows x to N.C inside the if - and crashed the compiler: the +// narrowing built a type reference out of `N.C` as the property access it is, and a type +// reference names its type with an entity name (an identifier or a qualified name). + +namespace N { + export class C { + code = 5; + } + + export namespace Inner { + export class D { + code = 7; + } + } +} + +function main() { + let a: any = new N.C(); + let r = 0; + if (a instanceof N.C) { + r = a.code; + } + + assert(r == 5, "narrowed to N.C"); + + let b: any = new N.Inner.D(); + let s = 0; + if (b instanceof N.Inner.D) { + s = b.code; + } + + assert(s == 7, "narrowed to N.Inner.D"); + + let t = 0; + if (b instanceof N.C) { + t = 1; + } + + assert(t == 0, "an N.Inner.D is not an N.C"); + + print("done."); +} diff --git a/tslang/test/tester/tests/export_throw_class.ts b/tslang/test/tester/tests/export_throw_class.ts new file mode 100644 index 000000000..31155eacd --- /dev/null +++ b/tslang/test/tester/tests/export_throw_class.ts @@ -0,0 +1,15 @@ +namespace M { + + // Thrown only from this module: the importer catches it without ever throwing it itself. + export class Failure { + code: number; + + constructor(code: number) { + this.code = code; + } + } + + export function fail(code: number) { + throw new Failure(code); + } +} diff --git a/tslang/test/tester/tests/import_throw_class.ts b/tslang/test/tester/tests/import_throw_class.ts new file mode 100644 index 000000000..6df642c48 --- /dev/null +++ b/tslang/test/tester/tests/import_throw_class.ts @@ -0,0 +1,66 @@ +import './export_throw_class' + +// Catching a class that only another module throws. +// - Linux: an untyped catch finds out what it caught from the exception's type_info, and only +// knew the classes its own module throws - so this class was boxed as a plain object and +// `e` threw an invalid cast. The class's type_info now carries a box thunk. +// - -shared: `instanceof` and `e` of a class imported from a shared library dropped +// the instanceOf call (the rtti value was expected to be a ref), so the first did not compile +// and the second always threw. +// - static link, Windows: a typed catch and the throw each emitted the class's box thunk, and +// the two collided (duplicate symbol). + +function typedCatch() { + let r: number = 0; + try { + M.fail(1); + } catch (e: M.Failure) { + r = e.code; + } + + return r; +} + +function untypedCatch() { + let r: number = 0; + try { + M.fail(2); + } catch (e) { + r = (e).code; + } + + return r; +} + +function anyCatch() { + let r: number = 0; + try { + M.fail(3); + } catch (e: any) { + r = (e).code; + } + + return r; +} + +function instanceofCatch() { + let r: number = 0; + try { + M.fail(4); + } catch (e: any) { + if (e instanceof M.Failure) { + r = e.code; + } + } + + return r; +} + +function main() { + assert(typedCatch() == 1, "catch (e: M.Failure) of a class thrown in another module"); + assert(untypedCatch() == 2, "e of a class thrown in another module"); + assert(anyCatch() == 3, "catch (e: any) of a class thrown in another module"); + assert(instanceofCatch() == 4, "instanceof on a caught class thrown in another module"); + + print("done."); +}