// Tests regarding Type.covariant // // See ../../README.md for information about DMD unit tests. module semantic.covariance; import dmd.astenums : STC, StorageClass; import dmd.func : FuncDeclaration; import dmd.mtype : Covariant, Type; import dmd.typesem : covariant; import support; @("sanity") unittest { testCovariant(Type.tvoid, Type.tvoid, Result(Covariant.yes)); testCovariant(Type.tvoid, Type.tint8, Result(Covariant.distinct)); } @("empty-params") unittest { Test test = { // Example code denoting the compared function types code: q{ void base(); void target(); }, // Test: typeof(base).covariant(typeof(target)) baseToTarget: { result: Covariant.yes, }, }; runTest(test); } @("param-count") unittest { Test test = { code: q{ void base(); void target(int); }, baseToTarget: { result: Covariant.distinct, }, }; runTest(test); } @("variadics") unittest { Test test = { code: q{ int base(); int target(...); }, baseToTarget: { result: Covariant.distinct, }, }; runTest(test); } @("variadics2") unittest { Test test = { code: q{ int base(...); int target(...); }, baseToTarget: { result: Covariant.yes, }, }; runTest(test); } @("missmatched-param-types") unittest { Test test = { code: q{ void base(string); void target(int); }, // Test: typeof(base).covariant(typeof(target)) baseToTarget: { result: Covariant.distinct, }, // Reversed test: typeof(target).covariant(typeof(base)) targetToBase: { result: Covariant.distinct } }; runTest(test); } @("class-params") unittest { Test test = { code: q{ void base(Throwable); void target(const Throwable); }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.yes } }; runTest(test); } @("struct-params") unittest { Test test = { code: q{ struct S { int* ptr; } void base(S); void target(const S); }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.yes } }; runTest(test); } @("pointer-params") unittest { Test test = { code: q{ void base(int*); void target(void**); }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } @("array-params") unittest { Test test = { code: q{ void base(const int[]); void target(int[]); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } @("function-pointer-params") unittest { Test test = { code: q{ void base(int function() @system); void target(int function() @safe); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } @("delegate-params") unittest { Test test = { code: q{ void base(int delegate() @system); void target(int delegate() @safe); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct, } }; runTest(test); } @("linkage") unittest { Test test = { code: q{ void base(); extern(C++) void target(); }, baseToTarget: { result: Covariant.no, }, }; runTest(test); } @("class-return") unittest { Test test = { code: q{ Throwable base(); const(Throwable) target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("struct-return") unittest { Test test = { code: q{ struct S { int* ptr; } S base(); const(S) target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("pointer-return") unittest { Test test = { code: q{ int* base(); void* target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("null-return") unittest { Test test = { code: q{ typeof(null) base(); void* target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("noreturn-return") unittest { Test test = { code: q{ noreturn base(); void* target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("different-ref-return") unittest { Test test = { code: q{ ref int base(); int target(); }, baseToTarget: { result: Covariant.no, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("different-scope") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections S base() scope; S target(); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("different-return") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections S base(); S target() return; } }, baseToTarget: { result: Covariant.yes, // Is this correct? }, targetToBase: { result: Covariant.no } }; runTest(test); } @("scope-vs-return") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections S base() scope; S target() return; } }, baseToTarget: { result: Covariant.no, }, targetToBase: { result: Covariant.no } }; runTest(test); } @("return-vs-ref") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections ref S base() scope; ref S target() return; } }, baseToTarget: { result: Covariant.yes, // Adding ref to the preceeding test makes them covariant?? }, targetToBase: { result: Covariant.no } }; runTest(test); } @("mutability") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections void base() const; void target(); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct // Should be no? } }; runTest(test); } @("attributes") unittest { Test test = { code: q{ void base() pure nothrow @nogc @safe; void target(); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.no, suggestion: STC.pure_ | STC.nothrow_ | STC.nogc | STC.safe } }; runTest(test); } @("function-pointer-param-mutability") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections void base(const int function()) const; void target(int function()); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct // Should be no? } }; runTest(test); } @("function-pointer-param-mutability-2") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections void base(int function()) const; void target(const int function()); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } @("delegate-param-mutability") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections void base(const int delegate()) const; void target(int delegate()); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct // Should be no? } }; runTest(test); } @("delegate-param-mutability-2") unittest { Test test = { code: q{ struct S { int* ptr; // Force indirections void base(int delegate()) const; void target(const int delegate()); } }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } // Deconstructing the failure observed in runnable/xtest46.d @("xtest46-base") unittest { Test test = { code: q{ static struct T { void base(ref int*); void target(ref const int*) const; } }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.yes } }; runTest(test); } @("xtest46-free-function") unittest { Test test = { code: q{ void base(void function(ref int*)); void target(void function(ref const int*)); }, baseToTarget: { result: Covariant.yes, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } @("xtest46-function") unittest { Test test = { code: q{ struct T { void base(void function(ref int*)); void target(void function(ref const int*)) const; } }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.distinct // Should this be yes? } }; runTest(test); } @("xtest46-delegate") unittest { Test test = { code: q{ // static assert(is(void delegate(ref const int*) : void delegate(ref int*))); struct T { void base(void delegate(ref int*)); void target(void delegate(ref const int*)) const; } }, baseToTarget: { result: Covariant.distinct, }, targetToBase: { result: Covariant.distinct } }; runTest(test); } //======================================================================================== // Utility types / methods // /// Test configuration declaring the input types and expected return values /// when applying `Type.covariant` in any direction struct Test { string code; /// Source code declaring the function `base` and `target` which denote the tested `TypeFunction`'s Result baseToTarget; /// Expected result of `typeof(base).covariant(typeof(target))` Result targetToBase; /// Expected result of `typeof(target).covariant(typeof(base))` } /// Expected output from `Type.Covariant` struct Result { Covariant result = cast(Covariant) -1; /// Return value StorageClass suggestion; /// Storage class suggestion returned in `pstc` } /++ + Parses the source code and verifies that + + => `typeof(base).covariant(typeof(target))` yields `test,baseToTarget` + + (Optionally) + => `typeof(target).covariant(typeof(base))` yields `test.targetToBase` + + Params: + test = test configuration + line = location used for error messages +/ void runTest(const ref Test test, const size_t line = __LINE__) { initializeFrontend(); scope (exit) deinitializeFrontend(); with (extractFunctions(test.code, line)) { testCovariant(base.type, target.type, test.baseToTarget, line); // Reversed test is optional if (test.targetToBase != Result.init) testCovariant(target.type, base.type, test.targetToBase, line); } } /// Wrapper struct holding the function declarations passed to `Type.covariant` struct Config { /// FuncDeclaration base, target; } /++ + Compiles the source code and searches the resulting AST for functions named + `base` and `target`. + + Params: + code = the source code + line = location used for error messages +/ Config extractFunctions(const string code, const size_t line) { import dmd.func : FuncDeclaration; import dmd.visitor : SemanticTimeTransitiveVisitor; auto result = compiles(code); enforce(!!result, line, "Semantic analysis failed with errors"); /// Visitor that searches the AST for the `FuncDeclaration`'s named `base` and `target` extern (C++) static final class TestVisitor : SemanticTimeTransitiveVisitor { Config config; alias visit = typeof(super).visit; override void visit(FuncDeclaration fd) { assert(fd); assert (fd.ident); if (fd.ident.toString() == "base") config.base = fd; else if (fd.ident.toString() == "target") config.target = fd; else assert(false, "Unexpected function!"); } } scope visitor = new TestVisitor(); (cast() result.module_).accept(visitor); enforce(!!visitor.config.base, line, "No FuncDeclaration `base` found!"); enforce(!!visitor.config.target, line, "No FuncDeclaration `target` found!"); return visitor.config; } /++ + Verifies that `base.covariant(target)` yields `expected`. + + Params: + base = replacement type + target = type to be substituted + expected = the expected return values + line = location used for error messages +/ void testCovariant(Type base, Type target, in Result expected, const size_t line = __LINE__) { import dmd.globals : global; assert(base); assert(target); STC actualStc; const actual = base.covariant(target, &actualStc); enforce(!global.errors, line, "`covariant` raised an error!"); enforce(actual == expected.result, line, cast(string) ( "Unexpected result!\n\n" ~ "base : `" ~ base.toString() ~ "`\n\n" ~ "target: `" ~ target.toString() ~ "`\n\n" ~ "`base.covariant(target)` yields " ~ toString(actual) ~ " instead of " ~ toString(expected.result) ~ '\n' )); enforce(actualStc == expected.suggestion, line, cast(string) ( "Unexpected suggestion!\n\n" ~ "base : `" ~ base.toString() ~ "`\n\n" ~ "target: `" ~ target.toString() ~ "`\n\n" ~ "`base.covariant(target)` suggests " ~ toString(actualStc) ~ " instead of " ~ toString(expected.suggestion) ~ '\n' )); } /++ + Converts the given `Covariant` value to `string`. + + Params: + cv = covariance value + + Returns: the string representation of `cv` +/ string toString(const Covariant cv) pure nothrow @safe { static immutable members = [ __traits(allMembers, Covariant) ]; if ((cast(ulong) cv) >= members.length) return ""; return members[cv]; } /++ + Formats the given StorageClass value as an array of STC's members, + e.g. `STC.const_ | STC.shared_` is printed as `[const_, shared_]`. + + Params: + stc = bitfield consisting of STC's entries + + Returns: the string representation of `stc` +/ string toString(const StorageClass stc) pure nothrow @safe { import core.bitop : popcnt; string result = "["; bool first = true; foreach (const member; __traits(allMembers, STC)) { enum val = __traits(getMember, STC, member); if ((stc & val) == 0 || popcnt(val) > 1) continue; if (first) first = false; else result ~= ", "; result ~= member; } result ~= "]"; return result; } /// Custom assert function that overrides the line number to point to the current test /// instead of the utility methods void enforce(const bool check, const size_t line, lazy const string msg) pure @safe { import core.exception : AssertError; if (!check) throw new AssertError(msg, __FILE__, line); } //======================================================================================== // Common setup identical to the other tests: // /// Initialize the frontend before each test @beforeEach void initializeFrontend() { import dmd.frontend : initDMD; initDMD(); } /// Deinitialize the frontend after each test @afterEach void deinitializeFrontend() { import dmd.frontend : deinitializeDMD; deinitializeDMD(); }