mirror of
https://github.com/dlang/dmd.git
synced 2025-04-27 21:51:03 +03:00
953 lines
18 KiB
D
953 lines
18 KiB
D
// 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 "<Malformed Covariant value>";
|
|
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();
|
|
}
|