diff --git a/changelog/fix16002.dd b/changelog/fix16002.dd new file mode 100644 index 000000000000..898b6c97f74c --- /dev/null +++ b/changelog/fix16002.dd @@ -0,0 +1,7 @@ +fix Issue 16002 - Add `is(sym == module)` and `is(sym == package)` + +This enhancement adds two new forms of the `is()`, expression, which +determine whether a given symbol represents a module or package. + +It also adds `__traits(isModule, sym)` and `__traits(isPackage, sym)`, which +do the same thing. diff --git a/src/dmd/dmodule.d b/src/dmd/dmodule.d index e3578d0fa6fc..50fa2449b32a 100644 --- a/src/dmd/dmodule.d +++ b/src/dmd/dmodule.d @@ -138,6 +138,71 @@ void semantic3OnDependencies(Module m) semantic3OnDependencies(m.aimports[i]); } +/** + * Converts a chain of identifiers to the filename of the module + * + * Params: + * packages = the names of the "parent" packages + * ident = the name of the child package or module + * + * Returns: + * the filename of the child package or module + */ +private const(char)[] getFilename(Identifiers* packages, Identifier ident) +{ + const(char)[] filename = ident.toString(); + + if (packages == null || packages.dim == 0) + return filename; + + OutBuffer buf; + OutBuffer dotmods; + auto modAliases = &global.params.modFileAliasStrings; + + void checkModFileAlias(const(char)[] p) + { + /* Check and replace the contents of buf[] with + * an alias string from global.params.modFileAliasStrings[] + */ + dotmods.writestring(p); + foreach_reverse (const m; *modAliases) + { + const q = strchr(m, '='); + assert(q); + if (dotmods.offset == q - m && memcmp(dotmods.peekChars(), m, q - m) == 0) + { + buf.reset(); + auto rhs = q[1 .. strlen(q)]; + if (rhs.length > 0 && (rhs[$ - 1] == '/' || rhs[$ - 1] == '\\')) + rhs = rhs[0 .. $ - 1]; // remove trailing separator + buf.writestring(rhs); + break; // last matching entry in ms[] wins + } + } + dotmods.writeByte('.'); + } + + foreach (pid; *packages) + { + const p = pid.toString(); + buf.writestring(p); + if (modAliases.dim) + checkModFileAlias(p); + version (Windows) + enum FileSeparator = '\\'; + else + enum FileSeparator = '/'; + buf.writeByte(FileSeparator); + } + buf.writestring(filename); + if (modAliases.dim) + checkModFileAlias(filename); + buf.writeByte(0); + filename = buf.extractSlice()[0 .. $ - 1]; + + return filename; +} + enum PKG : int { unknown, // not yet determined whether it's a package.d or not @@ -281,6 +346,27 @@ extern (C++) class Package : ScopeDsymbol } return null; } + + /** + * Checks for the existence of a package.d to set isPkgMod appropriately + * if isPkgMod == PKG.unknown + */ + final void resolvePKGunknown() + { + if (isModule()) + return; + if (isPkgMod != PKG.unknown) + return; + + Identifiers packages; + for (Dsymbol s = this.parent; s; s = s.parent) + packages.insert(0, s.ident); + + if (lookForSourceFile(getFilename(&packages, ident))) + Module.load(Loc(), &packages, this.ident); + else + isPkgMod = PKG.package_; + } } /*********************************************************** @@ -462,63 +548,8 @@ extern (C++) final class Module : Package // foo.bar.baz // into: // foo\bar\baz - const(char)[] filename = ident.toString(); - if (packages && packages.dim) - { - OutBuffer buf; - OutBuffer dotmods; - auto ms = global.params.modFileAliasStrings; - const msdim = ms ? ms.dim : 0; - - void checkModFileAlias(const(char)[] p) - { - /* Check and replace the contents of buf[] with - * an alias string from global.params.modFileAliasStrings[] - */ - dotmods.writestring(p); - Lmain: - for (size_t j = msdim; j--;) - { - const m = (*ms)[j]; - const q = strchr(m, '='); - assert(q); - if (dotmods.offset == q - m && memcmp(dotmods.peekChars(), m, q - m) == 0) - { - buf.reset(); - auto qlen = strlen(q + 1); - if (qlen && (q[qlen] == '/' || q[qlen] == '\\')) - --qlen; // remove trailing separator - buf.writestring(q[1 .. qlen + 1]); - break Lmain; // last matching entry in ms[] wins - } - } - dotmods.writeByte('.'); - } - - foreach (pid; *packages) - { - const p = pid.toString(); - buf.writestring(p); - if (msdim) - checkModFileAlias(p); - version (Windows) - { - buf.writeByte('\\'); - } - else - { - buf.writeByte('/'); - } - } - buf.writestring(filename); - if (msdim) - checkModFileAlias(filename); - buf.writeByte(0); - filename = buf.extractData().toDString(); - } - - /* Look for the source file - */ + const(char)[] filename = getFilename(packages, ident); + // Look for the source file if (const result = lookForSourceFile(filename)) filename = result; // leaks diff --git a/src/dmd/expressionsem.d b/src/dmd/expressionsem.d index 5e126ab49fe0..0cc14ada3b23 100644 --- a/src/dmd/expressionsem.d +++ b/src/dmd/expressionsem.d @@ -2308,6 +2308,36 @@ private bool functionParameters(const ref Loc loc, Scope* sc, return (err || olderrors != global.errors); } +/** + * Determines whether a symbol represents a module or package + * (Used as a helper for is(type == module) and is(type == package)) + * + * Params: + * sym = the symbol to be checked + * + * Returns: + * the symbol which `sym` represents (or `null` if it doesn't represent a `Package`) + */ +Package resolveIsPackage(Dsymbol sym) +{ + Package pkg; + if (Import imp = sym.isImport()) + { + if (imp.pkg is null) + { + .error(sym.loc, "Internal Compiler Error: unable to process forward-referenced import `%s`", + imp.toChars()); + assert(0); + } + pkg = imp.pkg; + } + else + pkg = sym.isPackage(); + if (pkg) + pkg.resolvePKGunknown(); + return pkg; +} + private Module loadStdMath() { __gshared Import impStdMath = null; @@ -5098,16 +5128,34 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } Type tded = null; - Scope* sc2 = sc.copy(); // keep sc.flags - sc2.tinst = null; - sc2.minst = null; - sc2.flags |= SCOPE.fullinst; - Type t = e.targ.trySemantic(e.loc, sc2); - sc2.pop(); - if (!t) - goto Lno; - // errors, so condition is false - e.targ = t; + if (e.tok2 == TOK.package_ || e.tok2 == TOK.module_) // These is() expressions are special because they can work on modules, not just types. + { + Dsymbol sym = e.targ.toDsymbol(sc); + if (sym is null) + goto Lno; + Package p = resolveIsPackage(sym); + if (p is null) + goto Lno; + if (e.tok2 == TOK.package_ && p.isModule()) // Note that isModule() will return null for package modules because they're not actually instances of Module. + goto Lno; + else if(e.tok2 == TOK.module_ && !(p.isModule() || p.isPackageMod())) + goto Lno; + tded = e.targ; + goto Lyes; + } + + { + Scope* sc2 = sc.copy(); // keep sc.flags + sc2.tinst = null; + sc2.minst = null; + sc2.flags |= SCOPE.fullinst; + Type t = e.targ.trySemantic(e.loc, sc2); + sc2.pop(); + if (!t) // errors, so condition is false + goto Lno; + e.targ = t; + } + if (e.tok2 != TOK.reserved) { switch (e.tok2) diff --git a/src/dmd/globals.d b/src/dmd/globals.d index 653af893b8b3..5cebb580567c 100644 --- a/src/dmd/globals.d +++ b/src/dmd/globals.d @@ -202,7 +202,7 @@ struct Param uint errorLimit = 20; const(char)[] argv0; // program name - Array!(const(char)*)* modFileAliasStrings; // array of char*'s of -I module filename alias strings + Array!(const(char)*) modFileAliasStrings; // array of char*'s of -I module filename alias strings Array!(const(char)*)* imppath; // array of char*'s of where to look for import modules Array!(const(char)*)* fileImppath; // array of char*'s of where to look for file import modules const(char)* objdir; // .obj/.lib file output directory diff --git a/src/dmd/globals.h b/src/dmd/globals.h index a24eba62b3b3..0d214b6b64dd 100644 --- a/src/dmd/globals.h +++ b/src/dmd/globals.h @@ -178,7 +178,7 @@ struct Param unsigned errorLimit; DArray argv0; // program name - Array *modFileAliasStrings; // array of char*'s of -I module filename alias strings + Array modFileAliasStrings; // array of char*'s of -I module filename alias strings Array *imppath; // array of char*'s of where to look for import modules Array *fileImppath; // array of char*'s of where to look for file import modules const char *objdir; // .obj/.lib file output directory diff --git a/src/dmd/id.d b/src/dmd/id.d index 756814ec042d..770c16b66b5d 100644 --- a/src/dmd/id.d +++ b/src/dmd/id.d @@ -393,6 +393,8 @@ immutable Msgtable[] msgtable = { "isFinalFunction" }, { "isOverrideFunction" }, { "isStaticFunction" }, + { "isModule" }, + { "isPackage" }, { "isRef" }, { "isOut" }, { "isLazy" }, diff --git a/src/dmd/mars.d b/src/dmd/mars.d index 03c91dbc3b9c..9eb62126b9c7 100644 --- a/src/dmd/mars.d +++ b/src/dmd/mars.d @@ -2251,8 +2251,6 @@ bool parseCommandLine(const ref Strings arguments, const size_t argc, ref Param { if (p[4] && strchr(p + 5, '=')) { - if (!params.modFileAliasStrings) - params.modFileAliasStrings = new Strings(); params.modFileAliasStrings.push(p + 4); } else diff --git a/src/dmd/parse.d b/src/dmd/parse.d index cd1ee639260b..73e030f64537 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -7974,8 +7974,9 @@ final class Parser(AST) : Lexer nextToken(); if (tok == TOK.equal && (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.super_ || token.value == TOK.enum_ - || token.value == TOK.interface_ || token.value == TOK.argumentTypes - || token.value == TOK.parameters || token.value == TOK.const_ && peek(&token).value == TOK.rightParentheses + || token.value == TOK.interface_ || token.value == TOK.package_ || token.value == TOK.module_ + || token.value == TOK.argumentTypes || token.value == TOK.parameters + || token.value == TOK.const_ && peek(&token).value == TOK.rightParentheses || token.value == TOK.immutable_ && peek(&token).value == TOK.rightParentheses || token.value == TOK.shared_ && peek(&token).value == TOK.rightParentheses || token.value == TOK.inout_ && peek(&token).value == TOK.rightParentheses || token.value == TOK.function_ diff --git a/src/dmd/traits.d b/src/dmd/traits.d index aa49587226c9..44415dce3b92 100644 --- a/src/dmd/traits.d +++ b/src/dmd/traits.d @@ -22,6 +22,8 @@ import dmd.canthrow; import dmd.dclass; import dmd.declaration; import dmd.denum; +import dmd.dimport; +import dmd.dmodule; import dmd.dscope; import dmd.dsymbol; import dmd.dsymbolsem; @@ -107,6 +109,8 @@ shared static this() "isFinalFunction", "isOverrideFunction", "isStaticFunction", + "isModule", + "isPackage", "isRef", "isOut", "isLazy", @@ -480,7 +484,7 @@ Expression semanticTraits(TraitsExp e, Scope* sc) return null; } - IntegerExp isX(T)(bool function(T) fp) + IntegerExp isX(T)(bool delegate(T) fp) { if (!dim) return False(); @@ -516,6 +520,14 @@ Expression semanticTraits(TraitsExp e, Scope* sc) alias isFuncX = isX!FuncDeclaration; alias isEnumMemX = isX!EnumMember; + Expression isPkgX(bool function(Package) fp) + { + return isDsymX((Dsymbol sym) { + Package p = resolveIsPackage(sym); + return (p !is null) && fp(p); + }); + } + if (e.ident == Id.isArithmetic) { return isTypeX(t => t.isintegral() || t.isfloating()); @@ -672,6 +684,20 @@ Expression semanticTraits(TraitsExp e, Scope* sc) return isFuncX(f => !f.needThis() && !f.isNested()); } + if (e.ident == Id.isModule) + { + if (dim != 1) + return dimError(1); + + return isPkgX(p => p.isModule() || p.isPackageMod()); + } + if (e.ident == Id.isPackage) + { + if (dim != 1) + return dimError(1); + + return isPkgX(p => p.isModule() is null); + } if (e.ident == Id.isRef) { if (dim != 1) diff --git a/test/compilable/imports/pkgmodule/package.d b/test/compilable/imports/pkgmodule/package.d new file mode 100644 index 000000000000..b6e98ff011b0 --- /dev/null +++ b/test/compilable/imports/pkgmodule/package.d @@ -0,0 +1,3 @@ +/// Used to test is(x == package) and is(x == module) + +module imports.pkgmodule; diff --git a/test/compilable/imports/pkgmodule/plainmodule.d b/test/compilable/imports/pkgmodule/plainmodule.d new file mode 100644 index 000000000000..948a87e5feee --- /dev/null +++ b/test/compilable/imports/pkgmodule/plainmodule.d @@ -0,0 +1,2 @@ +/// Used to test is(x == module) +module imports.pkgmodule.plainmodule; diff --git a/test/compilable/imports/plainpackage/plainmodule.d b/test/compilable/imports/plainpackage/plainmodule.d new file mode 100644 index 000000000000..9e9933b379bd --- /dev/null +++ b/test/compilable/imports/plainpackage/plainmodule.d @@ -0,0 +1,4 @@ +/// Used to test is(x == module) + +module imports.plainpackage.plainmodule; + diff --git a/test/compilable/test16002.d b/test/compilable/test16002.d new file mode 100644 index 000000000000..f3303c0e3107 --- /dev/null +++ b/test/compilable/test16002.d @@ -0,0 +1,24 @@ +module test.compilable.test16002; + +import imports.plainpackage.plainmodule; +import imports.pkgmodule.plainmodule; + +struct MyStruct; + +alias a = imports.plainpackage; +alias b = imports.pkgmodule.plainmodule; + +static assert(is(imports.plainpackage == package)); +static assert(is(a == package)); +static assert(!is(imports.plainpackage.plainmodule == package)); +static assert(!is(b == package)); +static assert(is(imports.pkgmodule == package)); +static assert(!is(MyStruct == package)); + +static assert(!is(imports.plainpackage == module)); +static assert(!is(a == module)); +static assert(is(imports.plainpackage.plainmodule == module)); +static assert(is(b == module)); +// This is supposed to work even though we haven't directly imported imports.pkgmodule. +static assert(is(imports.pkgmodule == module)); +static assert(!is(MyStruct == module)); diff --git a/test/compilable/traits.d b/test/compilable/traits.d index 04470687cc23..c582105c9de6 100644 --- a/test/compilable/traits.d +++ b/test/compilable/traits.d @@ -31,3 +31,26 @@ version (linux) static assert(__traits(getTargetInfo, "objectFormat") == "elf"); static assert(__traits(getTargetInfo, "cppStd") == 199711); + +import imports.plainpackage.plainmodule; +import imports.pkgmodule.plainmodule; + +struct MyStruct; + +alias a = imports.plainpackage; +alias b = imports.pkgmodule.plainmodule; + +static assert(__traits(isPackage, imports.plainpackage)); +static assert(__traits(isPackage, a)); +static assert(!__traits(isPackage, imports.plainpackage.plainmodule)); +static assert(!__traits(isPackage, b)); +static assert(__traits(isPackage, imports.pkgmodule)); +static assert(!__traits(isPackage, MyStruct)); + +static assert(!__traits(isModule, imports.plainpackage)); +static assert(!__traits(isModule, a)); +static assert(__traits(isModule, imports.plainpackage.plainmodule)); +static assert(__traits(isModule, b)); +// This is supposed to work even though we haven't directly imported imports.pkgmodule. +static assert(__traits(isModule, imports.pkgmodule)); +static assert(!__traits(isModule, MyStruct)); diff --git a/test/fail_compilation/test16002.d b/test/fail_compilation/test16002.d new file mode 100644 index 000000000000..80ae40b6b88b --- /dev/null +++ b/test/fail_compilation/test16002.d @@ -0,0 +1,15 @@ +/* +REQUIRED_ARGS: +PERMUTE_ARGS: +TEST_OUTPUT: +--- +fail_compilation/test16002.d(100): Error: undefined identifier `imports.nonexistent` +fail_compilation/test16002.d(101): Error: undefined identifier `imports.nonexistent` +--- +*/ + +module test.fail_compilation.test16002; + +#line 100 +enum A = is(imports.nonexistent == package); +enum B = is(imports.nonexistent == module); diff --git a/test/fail_compilation/traits.d b/test/fail_compilation/traits.d index cc28972f256e..6d5d77185d81 100644 --- a/test/fail_compilation/traits.d +++ b/test/fail_compilation/traits.d @@ -12,11 +12,21 @@ fail_compilation/traits.d(100): Error: `getTargetInfo` key `"not_a_target_info"` fail_compilation/traits.d(101): Error: string expected as argument of __traits `getTargetInfo` instead of `100` fail_compilation/traits.d(102): Error: expected 1 arguments for `getTargetInfo` but had 2 fail_compilation/traits.d(103): Error: expected 1 arguments for `getTargetInfo` but had 0 +fail_compilation/traits.d(200): Error: undefined identifier `imports.nonexistent` +fail_compilation/traits.d(201): Error: undefined identifier `imports.nonexistent` +fail_compilation/traits.d(202): Error: expected 1 arguments for `isPackage` but had 0 +fail_compilation/traits.d(203): Error: expected 1 arguments for `isModule` but had 0 --- */ #line 100 -enum A = __traits(getTargetInfo, "not_a_target_info"); -enum B = __traits(getTargetInfo, 100); -enum C = __traits(getTargetInfo, "cppRuntimeLibrary", "bits"); -enum D = __traits(getTargetInfo); +enum A1 = __traits(getTargetInfo, "not_a_target_info"); +enum B1 = __traits(getTargetInfo, 100); +enum C1 = __traits(getTargetInfo, "cppRuntimeLibrary", "bits"); +enum D1 = __traits(getTargetInfo); + +#line 200 +enum A2 = __traits(isPackage, imports.nonexistent); +enum B2 = __traits(isModule, imports.nonexistent); +enum C2 = __traits(isPackage); +enum D2 = __traits(isModule);