diff --git a/tslang/include/TypeScript/DataStructs.h b/tslang/include/TypeScript/DataStructs.h index a90a5aea5..4e558aa14 100644 --- a/tslang/include/TypeScript/DataStructs.h +++ b/tslang/include/TypeScript/DataStructs.h @@ -4,6 +4,7 @@ #include "TypeScript/TypeScriptCompiler/Defines.h" #include +#include struct CompileOptions { @@ -29,7 +30,9 @@ struct CompileOptions // `--entry-point`. Without that, a library whose root merely initializes a variable would // define `main` too, and two of them fail to link with "duplicate symbol: main". bool generateEntryPoint; - enum Exports exportOpt; + // --export filters: `all`, `none`, names or globs, `!name` to exclude; empty means the + // `export` keyword decides (see MLIRExportFilter.h) + std::vector exportFilters; bool embedExportDeclarations; std::string outputFolder; bool appendGCtorsToMethod; diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRExportFilter.h b/tslang/include/TypeScript/MLIRLogic/MLIRExportFilter.h new file mode 100644 index 000000000..b79d8d724 --- /dev/null +++ b/tslang/include/TypeScript/MLIRLogic/MLIRExportFilter.h @@ -0,0 +1,63 @@ +#ifndef TYPESCRIPT_MLIRGENLOGIC_EXPORTFILTER_H +#define TYPESCRIPT_MLIRGENLOGIC_EXPORTFILTER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/GlobPattern.h" + +#include + +namespace typescript +{ + // A filter is `all`, `none`, a name or glob matched against the short or namespaced + // name, or any of those prefixed with `!` to exclude. + inline bool matchesExportFilter(llvm::StringRef pattern, llvm::StringRef name, llvm::StringRef fullName) + { + if (pattern == "all") + { + return true; + } + + if (pattern == "none") + { + return false; + } + + auto glob = llvm::GlobPattern::create(pattern); + if (!glob) + { + llvm::consumeError(glob.takeError()); + return pattern == name || pattern == fullName; + } + + return (!name.empty() && glob->match(name)) || (!fullName.empty() && glob->match(fullName)); + } + + // Exclusions always win; without any including filter the `export` keyword decides. + inline bool isExportedByFilters(llvm::ArrayRef filters, llvm::StringRef name, llvm::StringRef fullName, + bool hasExportKeyword) + { + auto hasIncluding = false; + auto included = false; + for (auto &filter : filters) + { + llvm::StringRef pattern(filter); + if (pattern.consume_front("!")) + { + if (matchesExportFilter(pattern, name, fullName)) + { + return false; + } + + continue; + } + + hasIncluding = true; + included |= matchesExportFilter(pattern, name, fullName); + } + + return hasIncluding ? included : hasExportKeyword; + } +} // namespace typescript + +#endif // TYPESCRIPT_MLIRGENLOGIC_EXPORTFILTER_H diff --git a/tslang/include/TypeScript/TypeScriptCompiler/Defines.h b/tslang/include/TypeScript/TypeScriptCompiler/Defines.h index dca150fdd..7878bc096 100644 --- a/tslang/include/TypeScript/TypeScriptCompiler/Defines.h +++ b/tslang/include/TypeScript/TypeScriptCompiler/Defines.h @@ -17,13 +17,6 @@ enum Action RunJIT }; -enum Exports -{ - ExportsNotSet, - ExportAll, - IgnoreAll -}; - // How compiled code reclaims heap memory. There have always been three of these - the flag // this replaced meant "leak everything", not "collect differently" - but they were spelled // as one boolean. See docs/reference-counting-evaluation.md. diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index ec2ad56b8..8e7e88889 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -25,6 +25,7 @@ #include "TypeScript/MLIRLogic/MLIRDebugInfoHelper.h" #include "TypeScript/MLIRLogic/MLIRPrinter.h" #include "TypeScript/MLIRLogic/MLIRDeclarationPrinter.h" +#include "TypeScript/MLIRLogic/MLIRExportFilter.h" #include "TypeScript/MLIRLogic/TypeOfOpHelper.h" #include "TypeScript/VisitorAST.h" @@ -2275,19 +2276,59 @@ class MLIRGenImpl mlir::LogicalResult mlirGen(VariableDeclaration item, VariableClass varClass, const GenContext &genContext); - auto getExportModifier(Node node) -> boolean + // members are filtered by the name of the class or interface they belong to + StringRef getExportFilterName(Node node) { - if (compileOptions.exportOpt == ExportAll) + if (node->parent == SyntaxKind::ClassDeclaration || node->parent == SyntaxKind::ClassExpression + || node->parent == SyntaxKind::InterfaceDeclaration) { - return true; + node = node->parent; } - if (compileOptions.exportOpt == IgnoreAll) + if (node == SyntaxKind::ClassDeclaration || node == SyntaxKind::ClassExpression) { - return false; + return MLIRHelper::getName(node.as()->name, stringAllocator); } - return hasModifier(node, SyntaxKind::ExportKeyword); + if (node == SyntaxKind::InterfaceDeclaration) + { + return MLIRHelper::getName(node.as()->name, stringAllocator); + } + + if (node == SyntaxKind::EnumDeclaration) + { + return MLIRHelper::getName(node.as()->name, stringAllocator); + } + + if (node == SyntaxKind::TypeAliasDeclaration) + { + return MLIRHelper::getName(node.as()->name, stringAllocator); + } + + if (node == SyntaxKind::FunctionDeclaration || node == SyntaxKind::FunctionExpression + || node == SyntaxKind::ArrowFunction || node == SyntaxKind::MethodDeclaration) + { + return MLIRHelper::getName(node.as()->name, stringAllocator); + } + + return StringRef(); + } + + auto getExportModifier(Node node, StringRef name) -> boolean + { + auto hasExportKeyword = hasModifier(node, SyntaxKind::ExportKeyword); + if (compileOptions.exportFilters.empty()) + { + return hasExportKeyword; + } + + auto fullName = name.empty() ? StringRef() : getFullNamespaceName(name); + return isExportedByFilters(compileOptions.exportFilters, name, fullName, hasExportKeyword); + } + + auto getExportModifier(Node node) -> boolean + { + return getExportModifier(node, compileOptions.exportFilters.empty() ? StringRef() : getExportFilterName(node)); } mlir::LogicalResult mlirGen(VariableDeclarationList variableDeclarationListAST, const GenContext &genContext); diff --git a/tslang/lib/TypeScript/MLIRGenVariables.cpp b/tslang/lib/TypeScript/MLIRGenVariables.cpp index 0f264f2ca..a12e6b25e 100644 --- a/tslang/lib/TypeScript/MLIRGenVariables.cpp +++ b/tslang/lib/TypeScript/MLIRGenVariables.cpp @@ -874,13 +874,14 @@ namespace mlirgen varClass.isUsing = isUsing; + auto exportByDecorator = false; if (variableDeclarationListAST->parent) { varClass.isPublic = hasModifier(variableDeclarationListAST->parent, SyntaxKind::ExportKeyword); - varClass.isExport = getExportModifier(variableDeclarationListAST->parent); iterateDecorators(variableDeclarationListAST->parent, genContext, [&](StringRef name, SmallVector args) { if (name == DLL_EXPORT) { + exportByDecorator = true; varClass.isExport = true; } @@ -943,16 +944,25 @@ namespace mlirgen // folded away inside its module - a const function becomes the function and its global is // erased (isGlobalConstLambda) - but other modules reach it by symbol, so it has to be the // global they import: an importer reads `export const f = () => ...` out of variable `f`. - if (varClass.type == VariableType::Const && !isUsing && varClass.isExport && !varClass.isImport && !genContext.funcOp) - { - varClass.type = VariableType::Let; - } - for (auto &item : variableDeclarationListAST->declarations) { // we need it for support "undefined type" in 'let' without initialization item->parent = variableDeclarationListAST; - if (mlir::failed(mlirGen(item, varClass, genContext))) + + // --export filters by name, and one statement can declare several names + auto itemVarClass = varClass; + if (variableDeclarationListAST->parent && !exportByDecorator) + { + itemVarClass.isExport = getExportModifier( + variableDeclarationListAST->parent, MLIRHelper::getName(item->name, stringAllocator)); + } + + if (itemVarClass.type == VariableType::Const && !isUsing && itemVarClass.isExport && !itemVarClass.isImport && !genContext.funcOp) + { + itemVarClass.type = VariableType::Let; + } + + if (mlir::failed(mlirGen(item, itemVarClass, genContext))) { return mlir::failure(); } diff --git a/tslang/tslang/opts.cpp b/tslang/tslang/opts.cpp index 9da418583..983fb225c 100644 --- a/tslang/tslang/opts.cpp +++ b/tslang/tslang/opts.cpp @@ -20,7 +20,7 @@ extern cl::opt disableWarnings; extern cl::opt generateDebugInfo; extern cl::opt lldbDebugInfo; extern cl::opt TargetTriple; -extern cl::opt exportAction; +extern cl::list exportFilters; extern cl::opt enableBuiltins; extern cl::opt noDefaultLib; extern cl::opt outputFilename; @@ -47,7 +47,7 @@ CompileOptions prepareOptions() compileOptions.enableBuiltins = enableBuiltins.getValue(); compileOptions.noDefaultLib = noDefaultLib.getValue(); compileOptions.disableWarnings = disableWarnings.getValue(); - compileOptions.exportOpt = exportAction.getValue(); + compileOptions.exportFilters.assign(exportFilters.begin(), exportFilters.end()); compileOptions.embedExportDeclarations = embedExportDeclarationsAction.getValue(); compileOptions.generateDebugInfo = generateDebugInfo.getValue(); compileOptions.lldbDebugInfo = lldbDebugInfo.getValue(); diff --git a/tslang/tslang/tslang.cpp b/tslang/tslang/tslang.cpp index f6b295dd5..c76b9be5f 100644 --- a/tslang/tslang/tslang.cpp +++ b/tslang/tslang/tslang.cpp @@ -130,9 +130,8 @@ cl::opt disableWarnings("nowarn", cl::desc("Disable Warnings"), cl::cat(Ty cl::opt verifyOwnership("verify-ownership", cl::desc("Check that every slot taking a reference gives it back on every path out of the function, unwind paths included"), cl::cat(TypeScriptCompilerCategory)); cl::opt generateDebugInfo("di", cl::desc("Generate Debug Infomation"), cl::cat(TypeScriptCompilerCategory)); cl::opt lldbDebugInfo("lldb", cl::desc("Debug Infomation for LLDB"), cl::cat(TypeScriptCompilerCategory)); -cl::opt exportAction("export", cl::desc("Export Symbols. (Useful to compile the same code into 'lib' (static library) and/or 'dll/so' (dynamic library)) "), - cl::values(clEnumValN(ExportAll, "all", "export all symbols")), - cl::values(clEnumValN(IgnoreAll, "none", "ignore all exports")), +cl::list exportFilters("export", cl::desc("Export Symbols, comma separated: 'all', 'none', names or globs (short or namespaced, e.g. 'add' or 'M.*') to export, '!name' to exclude. Without an including filter the 'export' keyword decides. (Useful to compile the same code into 'lib' (static library) and/or 'dll/so' (dynamic library))"), + cl::value_desc("all|none|name|!name"), cl::ZeroOrMore, cl::MiscFlags::CommaSeparated, cl::cat(TypeScriptCompilerCategory)); cl::opt embedExportDeclarationsAction("embed-declarations", cl::desc("Embed declarations as member __decls_lib_XXXX. (Needed in 'import' statement)"), cl::init(true), cl::cat(TypeScriptCompilerCategory)); diff --git a/tslang/unittests/MLIRGen/CMakeLists.txt b/tslang/unittests/MLIRGen/CMakeLists.txt index 219de07d1..7e5e98ef4 100644 --- a/tslang/unittests/MLIRGen/CMakeLists.txt +++ b/tslang/unittests/MLIRGen/CMakeLists.txt @@ -1,6 +1,7 @@ add_mlir_unittest(MLIRGenTests TypeToString.cpp DeclarationPrinter.cpp + ExportFilter.cpp TypeHelper.cpp ) get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) diff --git a/tslang/unittests/MLIRGen/ExportFilter.cpp b/tslang/unittests/MLIRGen/ExportFilter.cpp new file mode 100644 index 000000000..4e6c56cf6 --- /dev/null +++ b/tslang/unittests/MLIRGen/ExportFilter.cpp @@ -0,0 +1,62 @@ +#include "TypeScript/MLIRLogic/MLIRExportFilter.h" + +#include "gtest/gtest.h" + +using namespace typescript; + +namespace +{ +bool exported(std::vector filters, llvm::StringRef name, llvm::StringRef fullName, bool keyword) +{ + return isExportedByFilters(filters, name, fullName, keyword); +} +} // namespace + +TEST(ExportFilterTest, no_filters_follow_export_keyword) +{ + EXPECT_TRUE(exported({}, "add", "M.add", true)); + EXPECT_FALSE(exported({}, "add", "M.add", false)); +} + +TEST(ExportFilterTest, all_and_none_keep_their_meaning) +{ + EXPECT_TRUE(exported({"all"}, "add", "M.add", false)); + EXPECT_FALSE(exported({"none"}, "add", "M.add", true)); +} + +TEST(ExportFilterTest, name_filter_exports_only_matching_names) +{ + EXPECT_TRUE(exported({"add"}, "add", "M.add", false)); + EXPECT_TRUE(exported({"M.add"}, "add", "M.add", false)); + EXPECT_FALSE(exported({"add"}, "sub", "M.sub", true)); + EXPECT_TRUE(exported({"add", "sub"}, "sub", "M.sub", false)); +} + +TEST(ExportFilterTest, glob_filter_matches_short_or_namespaced_name) +{ + EXPECT_TRUE(exported({"M.*"}, "add", "M.add", false)); + EXPECT_FALSE(exported({"N.*"}, "add", "M.add", true)); + EXPECT_TRUE(exported({"calc_?"}, "calc_1", "M.calc_1", false)); +} + +TEST(ExportFilterTest, exclusion_only_filters_the_export_keyword) +{ + EXPECT_TRUE(exported({"!sub"}, "add", "M.add", true)); + EXPECT_FALSE(exported({"!sub"}, "add", "M.add", false)); + EXPECT_FALSE(exported({"!sub"}, "sub", "M.sub", true)); +} + +TEST(ExportFilterTest, exclusion_wins_over_inclusion) +{ + EXPECT_FALSE(exported({"all", "!add"}, "add", "M.add", true)); + EXPECT_TRUE(exported({"all", "!add"}, "sub", "M.sub", false)); + EXPECT_FALSE(exported({"M.*", "!M.internal*"}, "internalHelper", "M.internalHelper", true)); + EXPECT_FALSE(exported({"!add", "add"}, "add", "M.add", true)); +} + +TEST(ExportFilterTest, unnamed_declaration_matches_only_all) +{ + EXPECT_TRUE(exported({"all"}, "", "", false)); + EXPECT_FALSE(exported({"*"}, "", "", true)); + EXPECT_TRUE(exported({"!add"}, "", "", true)); +}