├── .dir-locals.el ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── TODO.md ├── codecov.yml ├── dub.sdl ├── dub.selections.json ├── examples └── python-ctfe │ ├── .gitignore │ ├── Makefile │ ├── dub.sdl │ ├── dub.selections.json │ ├── source │ ├── python │ │ ├── conv │ │ │ ├── d_to_python.d │ │ │ └── python_to_d.d │ │ └── package.dpp │ └── python_wrap_ctfe.d │ └── tests │ └── test_wrap.py ├── reggaefile.d ├── source └── mirror │ ├── ctfe │ └── reflection.d │ ├── meta │ ├── reflection.d │ └── traits.d │ ├── package.d │ ├── rtti.d │ └── trait_enums.d └── tests ├── blub.d ├── main.d ├── modules ├── empty.d ├── extra.d ├── functions.d ├── imports.d ├── issues.d ├── package.d ├── problems.d ├── runtime.d ├── templates.d ├── traits.d ├── types.d └── variables.d └── ut ├── ctfe └── reflection │ ├── extra.d │ ├── functions.d │ ├── package.d │ ├── runtime.d │ ├── traits.d │ ├── types.d │ ├── variables.d │ └── wrap.d ├── issues.d ├── meta ├── reflection │ ├── functions.d │ ├── package.d │ ├── types.d │ └── variables.d └── traits.d ├── package.d └── rtti ├── any.d └── oop.d /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((d-mode 5 | (fldd-no-recurse-dir . t) 6 | (fldd-dub-configuration . "unittest"))) 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Dub Test 7 | strategy: 8 | matrix: 9 | os: 10 | - ubuntu-24.04 11 | - windows-2025 12 | #- macos-13 13 | dc: 14 | - dmd-2.111.0 15 | - dmd-2.109.1 16 | - ldc-1.40.1 17 | - ldc-1.40.0 18 | 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install D compiler 24 | uses: dlang-community/setup-dlang@v2 25 | with: 26 | compiler: ${{ matrix.dc }} 27 | 28 | - name: Run tests 29 | run: dub test -q --build=unittest-cov-ctfe 30 | 31 | - name: Build binary 32 | run: dub build -q 33 | 34 | - uses: codecov/codecov-action@v5.1.2 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | bin 3 | *.lst -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mirror - compile and run time reflection for D 2 | 3 | [![Actions Status](https://github.com/atilaneves/mirror/workflows/CI/badge.svg)](https://github.com/atilaneves/mirror/actions) 4 | [![Coverage](https://codecov.io/gh/atilaneves/mirror/branch/master/graph/badge.svg)](https://codecov.io/gh/atilaneves/mirror) 5 | 6 | ## A unified API for compile and runtime reflection in D 7 | 8 | D is known for its unparalled compile-time reflection, but the API to 9 | do so is distributed among the built-in `__traits` and the 10 | `std.traits` package in the standard library. There was no one single 11 | place with a unified API for doing reflection in D. This package 12 | solves that problem. 13 | 14 | Furthermore, it attempts to, at the same time, extend the compile-time 15 | reflection capabilities of D into the runtime realm *and* allow users 16 | to write "regular" code with mixins and CTFE instead of template 17 | metaprogramming. This is done by transforming types and symbols into 18 | strings. See the tests and the `mirror.ctfe` package for more. 19 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * CTFE 2 | - Unit tests 3 | - Module ctors and dtors 4 | - List of base classes, interfaces 5 | - UDAs 6 | - traits? 7 | - aggregate member functions 8 | - function attributes (@safe, pure, ...) 9 | - Working Python wrapper based on it 10 | - Function linkage 11 | 12 | * RTTI 13 | - Non-OOP types? 14 | - Relationship to tardy? 15 | - UDAs 16 | - Function linkage 17 | 18 | * Meta 19 | - Investigate describe-d for API ideas 20 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "tests" 3 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "mirror" 2 | authors "Atila Neves" 3 | description "Compile and run time reflection" 4 | license "boost" 5 | targetType "library" 6 | targetPath "bin" 7 | 8 | 9 | configuration "library" { 10 | 11 | } 12 | 13 | 14 | configuration "unittest" { 15 | targetType "executable" 16 | targetName "ut" 17 | 18 | mainSourceFile "tests/main.d" 19 | sourcePaths "tests" 20 | importPaths "tests" 21 | 22 | buildRequirements "silenceDeprecations" 23 | dflags "-preview=dip1000" "-preview=dip1008" 24 | dependency "unit-threaded" version="*" 25 | } 26 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "unit-threaded": "2.2.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/python-ctfe/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | source/python/package.d 3 | *.pyc -------------------------------------------------------------------------------- /examples/python-ctfe/Makefile: -------------------------------------------------------------------------------- 1 | dpp_version = 0.4.0 2 | dpp_pkg_dir = $(HOME)/.dub/packages/dpp-$(dpp_version) 3 | dpp_lock = $(dpp_pkg_dir)/dpp.lock 4 | dpp_dir = $(dpp_pkg_dir)/dpp 5 | dpp = $(dpp_dir)/bin/d++ 6 | 7 | PYTHON_INCLUDE_DIR = $(shell python -c 'from distutils.sysconfig import get_python_inc; print(get_python_inc())') 8 | 9 | .PHONY: test 10 | test: python_wrap_ctfe.so 11 | PYTHONPATH=$(shell pwd) pytest -s -vv 12 | 13 | python_wrap_ctfe.so: libpython_wrap_ctfe.so 14 | cp $< $@ 15 | 16 | .PHONY: libpython_wrap_ctfe.so 17 | libpython_wrap_ctfe.so: source/python/package.d 18 | dub build -q 19 | 20 | source/python/package.d: source/python/package.dpp $(dpp) 21 | $(dpp) --preprocess-only --include-path $(PYTHON_INCLUDE_DIR) $< 22 | 23 | $(dpp): $(dpp_lock) 24 | cd $(dpp_dir) && dub build -q --build=release 25 | 26 | $(dpp_lock): 27 | dub fetch dpp --version=$(dpp_version) 28 | -------------------------------------------------------------------------------- /examples/python-ctfe/dub.sdl: -------------------------------------------------------------------------------- 1 | name "python_wrap_ctfe" 2 | targetType "dynamicLibrary" 3 | 4 | dependency "mirror" path="../../" 5 | -------------------------------------------------------------------------------- /examples/python-ctfe/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "mirror": {"path":"../../"}, 5 | "unit-threaded": "0.10.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/python-ctfe/source/python/conv/d_to_python.d: -------------------------------------------------------------------------------- 1 | module python.conv.d_to_python; 2 | 3 | 4 | import python: PyObject; 5 | import std.traits: isIntegral; 6 | 7 | 8 | PyObject* toPython(T)(T value) if(isIntegral!T) { 9 | import python: PyLong_FromLong; 10 | return PyLong_FromLong(value); 11 | } 12 | -------------------------------------------------------------------------------- /examples/python-ctfe/source/python/conv/python_to_d.d: -------------------------------------------------------------------------------- 1 | module python.conv.python_to_d; 2 | 3 | 4 | import python: PyObject; 5 | import std.traits: isIntegral; 6 | 7 | 8 | T to(T)(PyObject* value) if(isIntegral!T) { 9 | import python: PyLong_AsLong; 10 | 11 | const ret = PyLong_AsLong(value); 12 | //if(ret > T.max || ret < T.min) throw new Exception("Overflow"); 13 | 14 | return cast(T) ret; 15 | } 16 | -------------------------------------------------------------------------------- /examples/python-ctfe/source/python/package.dpp: -------------------------------------------------------------------------------- 1 | module python; 2 | 3 | #include "Python.h" 4 | 5 | 6 | enum MethodArgs { 7 | Var = METH_VARARGS, 8 | Keywords = METH_KEYWORDS, 9 | Static = METH_STATIC, 10 | None = METH_NOARGS, 11 | O = METH_O, 12 | } 13 | 14 | 15 | auto pyModuleCreate(PyModuleDef* moduleDef) @nogc nothrow { 16 | return PyModule_Create(moduleDef); 17 | } 18 | 19 | auto pyModuleDef(A...)(auto ref A args) { 20 | import std.functional: forward; 21 | 22 | return PyModuleDef( 23 | // the line below is a manual D version expansion of PyModuleDef_HEAD_INIT 24 | PyModuleDef_Base(PyObject(1 /*ref count*/, null /*type*/), null /*m_init*/, 0/*m_index*/, null/*m_copy*/), 25 | forward!args 26 | ); 27 | } 28 | 29 | /** 30 | Helper function to create PyMethodDef structs. 31 | The strings are compile-time parameters to avoid passing GC-allocated memory 32 | to Python (by calling std.string.toStringz or manually appending the null 33 | terminator). 34 | */ 35 | auto pyMethodDef(string name, int flags = defaultMethodFlags, string doc = "", F) 36 | (F cfunction) pure 37 | { 38 | import std.traits: ReturnType, Parameters, isPointer; 39 | import std.meta: allSatisfy; 40 | 41 | static assert(isPointer!(ReturnType!F), 42 | "C function method implementation must return a pointer"); 43 | static assert(allSatisfy!(isPointer, Parameters!F), 44 | "C function method implementation must take pointers"); 45 | static assert(Parameters!F.length == 2 || Parameters!F.length == 3, 46 | "C function method implementation must take 2 or 3 pointers"); 47 | 48 | return PyMethodDef(name.ptr, cast(PyCFunction) cfunction, flags, doc.ptr); 49 | } 50 | 51 | 52 | enum defaultMethodFlags = MethodArgs.Var | MethodArgs.Keywords; 53 | -------------------------------------------------------------------------------- /examples/python-ctfe/source/python_wrap_ctfe.d: -------------------------------------------------------------------------------- 1 | import mirror.ctfe.reflection; 2 | 3 | 4 | import python; 5 | 6 | 7 | extern(C) export PyObject* PyInit_python_wrap_ctfe() { 8 | enum numMethods = 1; 9 | static PyMethodDef[numMethods + 1] methodDefs; 10 | methodDefs[0] = pyMethodDef!("add1")(&add1); 11 | 12 | static PyModuleDef moduleDef; 13 | moduleDef = pyModuleDef("python_wrap_ctfe".ptr, null /*doc*/, -1 /*size*/, methodDefs.ptr); 14 | 15 | return pyModuleCreate(&moduleDef); 16 | } 17 | 18 | 19 | private extern(C) PyObject* add1(PyObject* self, PyObject *args) nothrow { 20 | 21 | import python.conv.python_to_d: to; 22 | import python.conv.d_to_python: toPython; 23 | import std.conv: text; 24 | import std.string: toStringz; 25 | 26 | if(PyTuple_Size(args) != 2) { 27 | PyErr_SetString(PyExc_TypeError, text("Must use 2 arguments, not ", PyTuple_Size(args)).toStringz); 28 | return null; 29 | } 30 | 31 | auto arg0 = PyTuple_GetItem(args, 0); 32 | auto arg1 = PyTuple_GetItem(args, 1); 33 | 34 | return (arg0.to!int + arg1.to!int + 1).toPython; 35 | } 36 | -------------------------------------------------------------------------------- /examples/python-ctfe/tests/test_wrap.py: -------------------------------------------------------------------------------- 1 | def test_add1(): 2 | from python_wrap_ctfe import add1 3 | assert add1(1, 1) == 3 4 | assert add1(1, 2) == 4 5 | assert add1(2, 2) == 5 6 | -------------------------------------------------------------------------------- /reggaefile.d: -------------------------------------------------------------------------------- 1 | import reggae; 2 | mixin build!(dubTest!()); 3 | -------------------------------------------------------------------------------- /source/mirror/ctfe/reflection.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module provides the CTFE variant of compile-time reflection, 3 | allowing client code to use regular D functions (as opposed to 4 | template metaprogramming) to operate on the contents of a D module 5 | using string mixins. 6 | */ 7 | module mirror.ctfe.reflection; 8 | 9 | 10 | /* 11 | TODO: 12 | 13 | * Add types to test structs/classes/etc. 14 | 15 | * Add enums to test structs/classes/etc. 16 | 17 | * Fix default values. 18 | 19 | * Add static constructors to the module struct. This is going to be 20 | hard to test since by definition all of them will have already run 21 | before main. 22 | 23 | * When doing aggregates, include function return types and 24 | parameters, see the old `functions.allAggregates` test. 25 | */ 26 | 27 | 28 | Module module_(string moduleName)() { 29 | 30 | mixin(`static import `, moduleName, `;`); 31 | alias module_ = mixin(moduleName); 32 | auto mod = reflect!(module_, Module); 33 | mod.allAggregates = mod.aggregates; // FIXME 34 | 35 | return mod; 36 | } 37 | 38 | private auto reflect(alias container, T)() { 39 | import std.traits: moduleName; 40 | 41 | Variable[] variables; 42 | Function[] functionsByOverload; 43 | OverloadSet[] functionsBySymbol; 44 | Aggregate[] aggregates; 45 | UnitTest[] unitTests; 46 | 47 | static foreach(memberName; __traits(allMembers, container)) {{ 48 | 49 | // although this is fine even for a class, trying to pass this in 50 | // as a template parameter will fail. Using `agg.init` doesn't 51 | // work either. 52 | alias member = __traits(getMember, container, memberName); 53 | 54 | static if(is(typeof(member) == function) && isRegularFunction(memberName)) { 55 | functionsByOverload ~= overloads !(container, member, memberName); 56 | functionsBySymbol ~= overloadSet!(container, member, memberName); 57 | } else static if(is(member) && isUDT!member) 58 | aggregates ~= reflect!(member, Aggregate); 59 | // the first part only works for aggregates and isSymbolVariable only works for modules 60 | else static if((is(typeof(container.init)) && !is(TypeOf!member == function)) || 61 | isSymbolVariable!member) 62 | { 63 | auto var = newMember!(container, memberName, Variable); 64 | var.type = type!(typeof(member)); 65 | 66 | variables ~= var; 67 | } 68 | }} 69 | 70 | auto ret = newMember!(container, T); 71 | 72 | static if(__traits(hasMember, T, "kind")) 73 | ret.kind = Aggregate.toKind!container; 74 | 75 | ret.variables ~= variables; 76 | ret.aggregates = aggregates; 77 | ret.functionsByOverload = functionsByOverload; 78 | ret.functionsBySymbol = functionsBySymbol; 79 | 80 | static foreach(i, ut; __traits(getUnitTests, container)) {{ 81 | auto unitTest = newMember!(ut, UnitTest); 82 | unitTest.index = i; 83 | ret.unitTests ~= unitTest; 84 | }} 85 | 86 | return ret; 87 | } 88 | 89 | private auto newMember(alias member, T)() { 90 | mixin(newMemberImpl); 91 | } 92 | 93 | private auto newMember(alias parent, string identifier, T)() { 94 | alias member = __traits(getMember, parent, identifier); 95 | mixin(newMemberImpl); 96 | } 97 | 98 | private string newMemberImpl() @safe pure { 99 | return q{ 100 | import std.traits: moduleName; 101 | auto ret = new T; 102 | ret.fullyQualifiedName = __traits(fullyQualifiedName, member); 103 | ret.parent = __traits(fullyQualifiedName, __traits(parent, member)); 104 | ret.moduleName = moduleName!member; 105 | ret.visibilityStr = __traits(getVisibility, member); 106 | static if(__traits(compiles, __traits(getLinkage, member))) 107 | ret.linkageStr = __traits(getLinkage, member); 108 | static if(__traits(compiles, __traits(isNested, member))) 109 | ret.isNested = __traits(isNested, member); 110 | ret.isFuture = __traits(isFuture, member); 111 | ret.isDeprecated = __traits(isDeprecated, member); 112 | ret.isTemplate = __traits(isTemplate, member); 113 | ret.isModule = __traits(isModule, member); 114 | static if(__traits(compiles, __traits(getLocation, member))) { 115 | enum loc = __traits(getLocation, member); 116 | ret.location = Location(loc[0], loc[1], loc[2]); 117 | } 118 | 119 | static foreach(uda; __traits(getAttributes, member)) { 120 | static if(is(uda)) { 121 | ret.UDAs ~= TypeUDA.create!uda; 122 | } else static if(__traits(compiles, uda.init)) { 123 | ret.UDAs ~= new ValueUDA(uda); 124 | } else { 125 | ret.UDAs ~= SymbolUDA.create!uda; 126 | } 127 | } 128 | 129 | return ret; 130 | }; 131 | } 132 | 133 | 134 | private template TypeOf(alias T) { 135 | static if(is(T)) 136 | alias TypeOf = T; 137 | else static if(__traits(compiles, typeof(T))) 138 | alias TypeOf = typeof(T); 139 | else 140 | alias TypeOf = void; 141 | } 142 | 143 | private OverloadSet overloadSet(alias parent, alias symbol, string memberName)() { 144 | auto functions = overloads!(parent, symbol, memberName); 145 | return OverloadSet(__traits(fullyQualifiedName, parent) ~ "." ~ memberName, functions); 146 | } 147 | 148 | private Function[] overloads(alias parent, alias symbol, string memberName)() { 149 | import std.traits: moduleName; 150 | import std.algorithm: countUntil; 151 | 152 | Function[] ret; 153 | 154 | static foreach(i, overload; __traits(getOverloads, parent, memberName)) {{ 155 | 156 | static if(is(typeof(overload) R == return)) 157 | enum returnType = type!R; 158 | else 159 | static assert(false, "Cannot get return type of " ~ __traits(fullyQualifiedName, overload)); 160 | 161 | Parameter[] parameters; 162 | static if(is(typeof(overload) Ps == __parameters)) { 163 | static foreach(p; 0 .. Ps.length) {{ 164 | 165 | static if(is(typeof(__traits(identifier, Ps[p .. p + 1])))) 166 | enum paramIdentifier = __traits(identifier, Ps[p .. p + 1]); 167 | else 168 | enum paramIdentifier = Ps[i].stringof; 169 | 170 | enum paramString = Ps[p .. p + 1].stringof; 171 | enum assignIndex = paramString.countUntil(`=`); 172 | static if(assignIndex == -1) 173 | enum default_ = ""; 174 | else { 175 | // paramString will be something like: 176 | // `(T id = val)` 177 | // we want default_ in this case to be "val" 178 | static assert(paramString[assignIndex + 1] == ' '); 179 | enum default_ = paramString[assignIndex + 2 .. $-1]; 180 | } 181 | 182 | parameters ~= Parameter( 183 | type!(Ps[p]), 184 | paramIdentifier, 185 | phobosPSC([__traits(getParameterStorageClasses, overload, p)]), 186 | default_, 187 | ); 188 | }} 189 | } else 190 | static assert(false, "Cannot get parameters of " ~ __traits(fullyQualifiedName, overload)); 191 | 192 | auto func = newMember!(overload, Function); 193 | // override the fqn from `newMember` above since we want overriden methods 194 | func.fullyQualifiedName = __traits(fullyQualifiedName, parent) ~ "." ~ memberName; 195 | func.overloadIndex = i; 196 | func.returnType = returnType; 197 | func.parameters = parameters; 198 | func.isDisabled = __traits(isDisabled, overload); 199 | func.virtualIndex = __traits(getVirtualIndex, overload); 200 | func.isVirtualMethod = __traits(isVirtualMethod, overload); 201 | func.isAbstract = __traits(isAbstractFunction, overload); 202 | func.isFinal = __traits(isFinalFunction, overload); 203 | func.isOverride = __traits(isOverrideFunction, overload); 204 | func.isStatic = __traits(isStaticFunction, overload); 205 | func.isReturnOnStack = __traits(isReturnOnStack, overload); 206 | func.variadicStyle = mixin(`Function.VariadicStyle.`, __traits(getFunctionVariadicStyle, overload)); 207 | func.attributes = [ __traits(getFunctionAttributes, overload) ]; 208 | 209 | static if(__traits(compiles, () @safe { void* p = &overload; })) 210 | func.caller = &Caller!overload.impl; 211 | else static if(!__traits(isModule, __traits(parent, overload)) && 212 | !__traits(isAbstractFunction, overload)) 213 | func.caller = &Caller!overload.impl; 214 | 215 | ret ~= func; 216 | }} 217 | 218 | return ret; 219 | } 220 | 221 | 222 | private bool isRegularFunction(in string memberName) @safe pure nothrow { 223 | import std.algorithm: startsWith; 224 | return 225 | !memberName.startsWith("_sharedStaticCtor") && 226 | !memberName.startsWith("_staticCtor"); 227 | } 228 | 229 | private bool isUDT(Type)() { 230 | return 231 | is(Type == enum) || 232 | is(Type == struct) || 233 | is(Type == class) || 234 | is(Type == interface) || 235 | is(Type == union) 236 | ; 237 | } 238 | 239 | // look ma, no templates 240 | private auto phobosPSC(in string[] storageClasses) @safe pure nothrow { 241 | import std.traits: PSC = ParameterStorageClass; 242 | 243 | auto ret = PSC.none; 244 | 245 | foreach(storageClass; storageClasses) { 246 | final switch(storageClass) with(PSC) { 247 | case "scope": ret |= scope_; break; 248 | case "in": ret |= in_; break; 249 | case "out": ret |= out_; break; 250 | case "ref": ret |= ref_; break; 251 | case "lazy": ret |= lazy_; break; 252 | case "return": ret |= return_; break; 253 | } 254 | } 255 | 256 | return ret; 257 | } 258 | 259 | abstract class Member { 260 | string fullyQualifiedName; 261 | string moduleName; 262 | string parent; 263 | string visibilityStr; 264 | string linkageStr; 265 | bool isNested; 266 | bool isFuture; 267 | bool isDeprecated; 268 | bool isTemplate; 269 | bool isModule; 270 | Location location; 271 | UDA[] UDAs; 272 | 273 | abstract string aliasMixin() @safe pure scope const; 274 | 275 | final string identifier() @safe pure scope const { 276 | import std.string: split; 277 | return fullyQualifiedName.split(".")[$-1]; 278 | } 279 | 280 | final string importMixin() @safe pure scope const { 281 | return `static import ` ~ moduleName ~ `;`; 282 | } 283 | 284 | final Visibility visibility() @safe pure scope const { 285 | switch(visibilityStr) with(Visibility) { 286 | default: throw new Exception("Unknown visibility " ~ visibilityStr ~ " " ~ typeid(this).toString); 287 | static foreach(vis; ["public", "private", "protected", "export", "package"]) { 288 | case vis: return mixin(vis ~ "_"); 289 | } 290 | } 291 | } 292 | 293 | final Linkage linkage() @safe pure scope const { 294 | switch(linkageStr) with(Linkage) { 295 | default: throw new Exception("Unknown linkage " ~ linkageStr); 296 | case "D": return D; 297 | case "C": return C; 298 | case "C++": return Cplusplus; 299 | case "Windows": return Windows; 300 | case "ObjectiveC": return ObjectiveC; 301 | case "System": return System; 302 | } 303 | } 304 | } 305 | 306 | 307 | class Container: Member { 308 | 309 | Function[] functionsByOverload; 310 | OverloadSet[] functionsBySymbol; 311 | Aggregate[] aggregates; /// only the ones defined in the module. 312 | Variable[] variables; 313 | UnitTest[] unitTests; 314 | } 315 | 316 | class Module: Container { 317 | 318 | Aggregate[] allAggregates; /// includes all function return types. 319 | 320 | override string aliasMixin() @safe pure scope const { 321 | return fullyQualifiedName; 322 | } 323 | 324 | override string toString() @safe pure scope const { 325 | import std.conv: text; 326 | return text( 327 | `Module(`, 328 | fullyQualifiedName, `, `, 329 | functionsByOverload, `, `, 330 | functionsBySymbol, `, `, 331 | aggregates, `, `, 332 | allAggregates, `, `, 333 | variables, `, `, 334 | unitTests, `, `, 335 | `)` 336 | ); 337 | } 338 | } 339 | 340 | class Aggregate: Container { 341 | 342 | enum Kind { 343 | enum_, 344 | struct_, 345 | class_, 346 | interface_, 347 | union_, 348 | } 349 | 350 | Kind kind; 351 | 352 | static Kind toKind(T)() { 353 | with(Kind) { 354 | static foreach(k; ["enum", "struct", "class", "interface", "union"]) { 355 | static if(mixin(`is(T == `, k, `)`)) 356 | return mixin(k ~ "_"); 357 | } 358 | } 359 | } 360 | 361 | override string aliasMixin() @safe pure scope const { 362 | return `__traits(getMember, ` ~ parent ~ `, "` ~ identifier ~ `")`; 363 | } 364 | } 365 | 366 | struct OverloadSet { 367 | string fullyQualifiedName; 368 | Function[] overloads; 369 | 370 | invariant { assert(overloads.length > 0); } 371 | 372 | string importMixin() @safe pure const scope { 373 | return overloads[0].importMixin; 374 | } 375 | } 376 | 377 | class Function: Member { 378 | 379 | import std.variant: Variant; 380 | 381 | enum VariadicStyle { 382 | none, 383 | stdarg, 384 | argptr, 385 | typesafe, 386 | } 387 | 388 | size_t overloadIndex; 389 | Type returnType; 390 | Parameter[] parameters; 391 | bool isDisabled; 392 | size_t virtualIndex; 393 | bool isVirtualMethod; 394 | bool isAbstract; 395 | bool isFinal; 396 | bool isOverride; 397 | bool isStatic; 398 | bool isReturnOnStack; 399 | VariadicStyle variadicStyle; 400 | string[] attributes; 401 | alias Caller = Variant function(void*, Variant[]); 402 | Caller caller; 403 | 404 | override string aliasMixin() @safe pure scope const { 405 | import std.conv: text; 406 | return text(`__traits(getOverloads, `, this.parent, `, "`, this.identifier, `")[`, overloadIndex, `]`); 407 | } 408 | 409 | final Variant opCall(Variant[] args = []) const 410 | in(caller !is null) 411 | { 412 | return caller(null, args); 413 | } 414 | 415 | final Variant opCall(void* context, Variant[] args = []) const 416 | in(context !is null) 417 | in(caller !is null) 418 | { 419 | return caller(context, args); 420 | } 421 | 422 | final R funCall(R = void, A...)(A args) const { 423 | Variant[A.length] variants; 424 | static foreach(i; 0 .. A.length) variants[i] = args[i]; 425 | 426 | auto helper() { 427 | return opCall(variants[]); 428 | } 429 | 430 | static if(is(R == void)) 431 | helper; 432 | else 433 | return helper.get!R; 434 | } 435 | 436 | final R methodCall(R = void, A...)(void* context, A args) const { 437 | Variant[A.length] variants; 438 | static foreach(i; 0 .. A.length) variants[i] = args[i]; 439 | 440 | auto helper() { 441 | return opCall(context, variants[]); 442 | } 443 | 444 | static if(is(R == void)) 445 | helper; 446 | else 447 | return helper.get!R; 448 | } 449 | } 450 | 451 | 452 | struct Type { 453 | string fullyQualifiedName; 454 | bool isArithmetic; 455 | bool isFloating; 456 | bool isIntegral; 457 | bool isScalar; 458 | bool isUnsigned; 459 | bool isStaticArray; 460 | bool isAssociativeArray; 461 | bool isAbstractClass; 462 | bool isFinalClass; 463 | bool isCopyable; 464 | bool isPOD; 465 | bool isZeroInit; 466 | bool hasCopyConstructor; 467 | bool hasMoveConstructor; 468 | bool hasPostblit; 469 | string[] aliasThis; 470 | size_t[] pointerBitmap; 471 | size_t classInstanceSize; 472 | size_t classInstanceAlignment; 473 | 474 | string toString() @safe pure scope const { 475 | return fullyQualifiedName.idup; 476 | } 477 | } 478 | 479 | auto type(T)() { 480 | Type ret; 481 | ret.fullyQualifiedName = __traits(fullyQualifiedName, T); 482 | 483 | enum boolTraits = [ 484 | "isArithmetic", "isFloating", "isIntegral", "isScalar", "isUnsigned", "isStaticArray", 485 | "isAssociativeArray", "isAbstractClass", "isFinalClass", "isCopyable", "isPOD", 486 | "isZeroInit", "hasCopyConstructor", 487 | //"hasMoveConstructor", ??? 488 | "hasPostblit", 489 | ]; 490 | static foreach(trait; boolTraits) { 491 | mixin(`ret.`, trait, ` = __traits(`, trait, `, T);`); 492 | } 493 | 494 | ret.aliasThis = [ __traits(getAliasThis, T) ]; 495 | static if(__traits(compiles, __traits(getPointerBitmap, T))) 496 | ret.pointerBitmap = __traits(getPointerBitmap, T); 497 | 498 | static if(is(T == class)) { 499 | ret.classInstanceSize = __traits(classInstanceSize, T); 500 | ret.classInstanceAlignment = __traits(classInstanceAlignment, T); 501 | } 502 | 503 | return ret; 504 | } 505 | 506 | 507 | struct Parameter { 508 | import std.traits: PSC = ParameterStorageClass; 509 | 510 | Type type; 511 | string identifier; 512 | PSC storageClass; 513 | string default_; 514 | } 515 | 516 | enum Visibility { 517 | public_, 518 | private_, 519 | protected_, 520 | export_, 521 | package_, 522 | } 523 | 524 | 525 | enum Linkage { 526 | D, 527 | C, 528 | Cplusplus, 529 | Windows, 530 | ObjectiveC, 531 | System, 532 | } 533 | 534 | 535 | class Variable: Member { 536 | Type type; 537 | 538 | override string aliasMixin() @safe pure scope const { 539 | return `__traits(getMember, ` ~ parent ~ `, "` ~ this.identifier ~ `")`; 540 | } 541 | } 542 | 543 | private bool isSymbolVariable(alias symbol)() { 544 | return 545 | is(typeof(symbol)) 546 | && !is(typeof(symbol) == function) 547 | && !is(typeof(symbol) == void) // can happen with templates 548 | && is(typeof(symbol.init)) 549 | ; 550 | } 551 | 552 | private bool isTypeVariable(T)() { 553 | return 554 | is(T) 555 | && !is(T == function) 556 | && !is(T == void) // can happen with templates 557 | && is(typeof(T.init)) 558 | ; 559 | } 560 | 561 | 562 | string moduleName(T)(auto ref T obj) { 563 | import std.string: split, join; 564 | return obj.fullyQualifiedName.split(".")[0 .. $-1].join("."); 565 | } 566 | 567 | string identifier(T)(auto ref T obj) { 568 | import std.string: split, join; 569 | return obj.fullyQualifiedName.split(".")[$-1]; 570 | } 571 | 572 | 573 | class UnitTest: Member { 574 | size_t index; 575 | 576 | override string aliasMixin() @safe pure nothrow scope const { 577 | import std.conv: text; 578 | return text(`__traits(getUnitTests, `, parent, `)[`, index, `]`); 579 | } 580 | } 581 | 582 | 583 | struct Location { 584 | string file; 585 | size_t line; 586 | size_t column; 587 | } 588 | 589 | class UDA { 590 | override string toString() @safe pure scope const { 591 | assert(0); 592 | } 593 | } 594 | 595 | class ValueUDA: UDA { 596 | Type type; 597 | string value; 598 | 599 | this(T)(T value) { 600 | import std.conv: text; 601 | this.type = .type!T; 602 | this.value = value.text; 603 | } 604 | 605 | override string toString() @safe pure scope const { 606 | import std.conv: text; 607 | return text(`ValueUDA(`, type, `, `, value, `)`); 608 | } 609 | 610 | override bool opEquals(Object other) @safe pure scope const { 611 | auto otherValue = cast(ValueUDA) other; 612 | if(!otherValue) return false; 613 | return type == otherValue.type && value == otherValue.value; 614 | } 615 | } 616 | 617 | class TypeUDA: UDA { 618 | Type type; 619 | 620 | static create(T)() { 621 | auto ret = new TypeUDA; 622 | ret.type = .type!T; 623 | return ret; 624 | } 625 | 626 | override string toString() @safe pure scope const { 627 | import std.conv: text; 628 | return text(`TypeUDA(`, type, `)`); 629 | } 630 | 631 | override bool opEquals(Object other) @safe pure scope const { 632 | auto otherType = cast(TypeUDA) other; 633 | if(!otherType) return false; 634 | return type == otherType.type; 635 | } 636 | } 637 | 638 | class SymbolUDA: UDA { 639 | string symbol; 640 | 641 | static create(alias S)() { 642 | import std.traits: fullyQualifiedName; 643 | auto ret = new SymbolUDA; 644 | ret.symbol = fullyQualifiedName!S; 645 | return ret; 646 | } 647 | 648 | override string toString() @safe pure scope const { 649 | import std.conv: text; 650 | return text(`SymbolUDA(`, symbol, `)`); 651 | } 652 | 653 | override bool opEquals(Object other) @safe pure scope const { 654 | auto otherSymbol = cast(SymbolUDA) other; 655 | if(!otherSymbol) return false; 656 | return symbol == otherSymbol.symbol; 657 | } 658 | } 659 | 660 | 661 | mixin template registerModule(string moduleName = __MODULE__) { 662 | 663 | immutable mirror.ctfe.reflection.Module gModuleInfo; 664 | 665 | shared static this() @safe nothrow { 666 | gModuleInfo = module_!(moduleName); 667 | allModuleInfos ~= gModuleInfo; 668 | } 669 | } 670 | 671 | // shared immutable seems silly but otherwise there's a copy per 672 | // thread. 673 | shared immutable(Module)[] allModuleInfos; 674 | 675 | 676 | template Caller(alias F) { 677 | 678 | import std.variant: Variant; 679 | import std.typecons: Tuple; 680 | import std.traits: Parameters, ReturnType, fullyQualifiedName, Unqual; 681 | import std.conv: text; 682 | import std.string: replace; 683 | import std.functional: toDelegate; 684 | import std.meta: staticMap; 685 | 686 | Tuple!(staticMap!(Unqual, Parameters!F)) args; 687 | 688 | enum isFreeFunction = __traits(isModule, __traits(parent, F)); 689 | static if(isFreeFunction) 690 | alias FuncPtr = typeof(&F); 691 | else 692 | alias FuncPtr = typeof(toDelegate(&F)); 693 | 694 | Variant impl(void* context, Variant[] variantArgs) { 695 | 696 | FuncPtr fptr; 697 | 698 | static if(isFreeFunction) 699 | fptr = &F; 700 | else { 701 | fptr.funcptr = &F; 702 | fptr.ptr = context; 703 | } 704 | 705 | if(variantArgs.length != Parameters!F.length) 706 | throw new Exception( 707 | text("Cannot call `", fullyQualifiedName!F, "` with ", 708 | variantArgs.length, " arguments. Expected: ", Parameters!F.length)); 709 | 710 | static foreach(i; 0 .. args.length) { 711 | try 712 | args[i] = variantArgs[i].get!(Parameters!F[i]); 713 | catch(Exception) 714 | throw new Exception( 715 | text("Expected argument #", i, " of `", fullyQualifiedName!F, 716 | "` to be `", fullyQualifiedName!(Parameters!F[i]), "`, got: `", variantArgs[i], "`")); 717 | } 718 | 719 | auto helper() { 720 | return fptr(args.expand); 721 | } 722 | static if(is(ReturnType!F == void)) { 723 | helper; 724 | return Variant(); 725 | } else 726 | return Variant(helper); 727 | } 728 | } 729 | -------------------------------------------------------------------------------- /source/mirror/meta/reflection.d: -------------------------------------------------------------------------------- 1 | /** 2 | This module provides the template metaprogramming variant of compile-time 3 | reflection, allowing client code to do type-level computations on the 4 | contents of a D module. 5 | */ 6 | module mirror.meta.reflection; 7 | 8 | 9 | import mirror.trait_enums: Protection, toProtection, Linkage, toLinkage; 10 | import std.meta: Alias; 11 | 12 | /** 13 | Compile-time information on a D module. 14 | */ 15 | template Module(string moduleName) { 16 | 17 | import mirror.meta.traits: RecursiveTypeTree, RecursiveFieldTypes, FundamentalType, PublicMembers, 18 | MemberFunctionsByOverload; 19 | import std.meta: Alias, NoDuplicates, Filter, staticMap, templateNot; 20 | 21 | mixin(`import `, moduleName, `;`); 22 | private alias mod = Alias!(mixin(moduleName)); 23 | 24 | private alias publicMembers = PublicMembers!mod; 25 | 26 | /// User-defined structs/classes 27 | alias Aggregates = aggregates!publicMembers; 28 | private enum isEnum(T) = is(T == enum); 29 | private alias memberFunctions = 30 | staticMap!(MemberFunctionsByOverload, 31 | Filter!(templateNot!isEnum, Aggregates)); 32 | 33 | private template isAggregate(T) { 34 | alias U = FundamentalType!T; 35 | enum isAggregate = is(U == enum) || is(U == struct) || is(U == class) || is(U == interface) || is(U == union); 36 | } 37 | 38 | /// User-defined structs/classes and all types contained in them 39 | alias AggregatesTree = Filter!(isAggregate, RecursiveTypeTree!Aggregates); 40 | 41 | /// Global variables/enums 42 | alias Variables = variables!publicMembers; 43 | 44 | /// List of functions by symbol - contains overloads for each entry 45 | alias FunctionsBySymbol = functionsBySymbol!(mod, publicMembers); 46 | 47 | /// List of functions by overload - each overload is a separate entry 48 | alias FunctionsByOverload = functionsByOverload!(mod, publicMembers); 49 | 50 | alias AllFunctionReturnTypes = NoDuplicates!(returnTypes!FunctionsByOverload, returnTypes!memberFunctions); 51 | alias AllFunctionReturnTypesTree = RecursiveTypeTree!AllFunctionReturnTypes; 52 | 53 | alias AllFunctionParameterTypes = NoDuplicates!(parameterTypes!FunctionsByOverload, parameterTypes!memberFunctions); 54 | alias AllFunctionParameterTypesTree = RecursiveTypeTree!AllFunctionParameterTypes; 55 | 56 | /** 57 | All aggregates, including explicitly defined and appearing in 58 | function signatures 59 | */ 60 | alias AllAggregates = 61 | NoDuplicates!( 62 | staticMap!(FundamentalType, 63 | Filter!(isAggregate, 64 | AggregatesTree, AllFunctionReturnTypesTree, AllFunctionParameterTypesTree) 65 | ) 66 | ); 67 | } 68 | 69 | 70 | private template returnTypes(functions...) { 71 | 72 | import mirror.meta.traits: FundamentalType; 73 | import std.traits: ReturnType; 74 | import std.meta: staticMap, NoDuplicates; 75 | 76 | private template symbol(alias F) { 77 | import std.traits: isSomeFunction; 78 | static if(isSomeFunction!F) 79 | alias symbol = F; 80 | else 81 | alias symbol = F.symbol; 82 | } 83 | 84 | alias returnTypes = 85 | NoDuplicates!(staticMap!(FundamentalType, 86 | staticMap!(ReturnType, 87 | staticMap!(symbol, functions)))); 88 | } 89 | 90 | private template parameterTypes(functions...) { 91 | 92 | import mirror.meta.traits: FundamentalType; 93 | import std.traits: Parameters; 94 | import std.meta: staticMap, NoDuplicates; 95 | 96 | private template symbol(alias F) { 97 | import std.traits: isSomeFunction; 98 | static if(isSomeFunction!F) 99 | alias symbol = F; 100 | else 101 | alias symbol = F.symbol; 102 | } 103 | 104 | alias parameterTypes = 105 | NoDuplicates!(staticMap!(FundamentalType, 106 | staticMap!(Parameters, 107 | staticMap!(symbol, functions)))); 108 | } 109 | 110 | 111 | // User-defined types 112 | package template aggregates(publicMembers...) { 113 | import std.meta: staticMap, Filter; 114 | 115 | private template memberIsType(alias member) { 116 | import std.traits: isType; 117 | enum memberIsType = isType!(member.symbol); 118 | } 119 | 120 | private alias symbolOf(alias member) = member.symbol; 121 | 122 | alias aggregates = staticMap!(symbolOf, Filter!(memberIsType, publicMembers)); 123 | } 124 | 125 | 126 | // Global variables 127 | private template variables(publicMembers...) { 128 | import mirror.meta.traits: isMutableSymbol, isVariable; 129 | import std.meta: staticMap, Filter; 130 | 131 | private template toVariable(alias member) { 132 | alias T = member.Type; 133 | enum id = member.identifier; 134 | 135 | static if(__traits(compiles, Variable!(T, id, member.symbol, !isMutableSymbol!(member.symbol)))) 136 | alias toVariable = Variable!(T, id, member.symbol, !isMutableSymbol!(member.symbol)); 137 | else 138 | alias toVariable = Variable!(T, id, T.init, !isMutableSymbol!(member.symbol)); 139 | } 140 | 141 | alias variables = staticMap!(toVariable, Filter!(isVariable, publicMembers)); 142 | } 143 | 144 | /** 145 | A global variable. 146 | */ 147 | template Variable(T, string N, alias V, bool C) { 148 | alias Type = T; 149 | enum identifier = N; 150 | enum value = V; 151 | enum isConstant = C; 152 | } 153 | 154 | template FunctionsBySymbol(alias parent) { 155 | import mirror.meta.traits : PublicMembers; 156 | alias FunctionsBySymbol = functionsBySymbol!(parent, PublicMembers!parent); 157 | } 158 | 159 | private template functionsBySymbol(alias parent, publicMembers...) { 160 | 161 | import mirror.meta.traits: memberIsRegularFunction; 162 | import std.meta: Filter, staticMap; 163 | 164 | private alias functionMembers = Filter!(memberIsRegularFunction, publicMembers); 165 | 166 | private alias toFunction(alias member) = FunctionSymbol!( 167 | member.symbol, 168 | __traits(getProtection, member.symbol).toProtection, 169 | __traits(getLinkage, member.symbol).toLinkage, 170 | member.identifier, 171 | parent, 172 | ); 173 | 174 | alias functionsBySymbol = staticMap!(toFunction, functionMembers); 175 | } 176 | 177 | 178 | /** 179 | A function symbol with nested overloads. 180 | */ 181 | template FunctionSymbol( 182 | alias F, 183 | Protection P = __traits(getProtection, F).toProtection, 184 | Linkage L = __traits(getLinkage, F).toLinkage, 185 | string I = __traits(identifier, F), 186 | alias Parent = Alias!(__traits(parent, F)), 187 | ) 188 | { 189 | import std.meta: staticMap; 190 | 191 | alias symbol = F; 192 | enum identifier = I; 193 | alias parent = Parent; 194 | 195 | private alias toOverload(alias symbol) = FunctionOverload!( 196 | symbol, 197 | __traits(getProtection, symbol).toProtection, 198 | __traits(getLinkage, symbol).toLinkage, 199 | identifier, 200 | parent, 201 | ); 202 | 203 | alias overloads = staticMap!(toOverload, __traits(getOverloads, parent, identifier)); 204 | 205 | string toString() @safe pure { 206 | import std.conv: text; 207 | return text(`Function(`, overloads.stringof, ")"); 208 | } 209 | } 210 | 211 | template FunctionsByOverload(alias parent) { 212 | import mirror.meta.traits : PublicMembers; 213 | alias FunctionsByOverload = functionsByOverload!(parent, PublicMembers!parent); 214 | } 215 | 216 | private template functionsByOverload(alias parent, publicMembers...) { 217 | 218 | import mirror.meta.traits: memberIsRegularFunction; 219 | import std.meta: Filter, staticMap; 220 | 221 | private alias functionMembers = Filter!(memberIsRegularFunction, publicMembers); 222 | 223 | private template overload(alias S, string I, size_t Idx) { 224 | alias symbol = S; 225 | enum identifier = I; 226 | enum index = Idx; 227 | } 228 | 229 | template symbolsWithIndex(A...) { 230 | import std.range: iota; 231 | import std.meta: aliasSeqOf, staticMap; 232 | 233 | template Result(alias S, size_t I) { 234 | alias symbol = S; 235 | enum index = I; 236 | } 237 | 238 | alias toResult(size_t I) = Result!(A[I], I); 239 | 240 | alias symbolsWithIndex = staticMap!(toResult, aliasSeqOf!(A.length.iota)); 241 | } 242 | 243 | private template memberToOverloads(alias member) { 244 | private template isPublic(alias S) { 245 | enum isPublic = __traits(getProtection, S.symbol) == "public" 246 | || __traits(getProtection, S.symbol) == "export"; 247 | } 248 | alias overloadsWithIndex = symbolsWithIndex!(__traits(getOverloads, parent, member.identifier)); 249 | // the reason we need to filter here is that some of the overloads might be private 250 | private alias overloadSymbols = Filter!(isPublic, overloadsWithIndex); 251 | private alias toOverload(alias symbol) = overload!(symbol.symbol, member.identifier, symbol.index); 252 | alias memberToOverloads = staticMap!(toOverload, overloadSymbols); 253 | } 254 | 255 | private alias toFunction(alias overload) = FunctionOverload!( 256 | overload.symbol, 257 | __traits(getProtection, overload.symbol).toProtection, 258 | __traits(getLinkage, overload.symbol).toLinkage, 259 | overload.identifier, 260 | parent, 261 | overload.index, 262 | ); 263 | 264 | alias functionsByOverload = staticMap!(toFunction, staticMap!(memberToOverloads, functionMembers)); 265 | } 266 | 267 | 268 | /** 269 | A specific overload of a function. In most cases it will be 270 | synonymous with the function symbol since most functions aren't 271 | overloaded. 272 | */ 273 | template FunctionOverload( 274 | alias F, 275 | Protection P = __traits(getProtection, F).toProtection, 276 | Linkage L = __traits(getLinkage, F).toLinkage, 277 | string I = __traits(identifier, F), 278 | alias Parent = Alias!(__traits(parent, F)), 279 | size_t Idx = 0, 280 | ) 281 | { 282 | import mirror.meta.traits: Parameters; 283 | import std.traits: RT = ReturnType; 284 | 285 | alias symbol = F; 286 | alias protection = P; 287 | alias linkage = L; 288 | enum identifier = I; 289 | alias parent = Parent; 290 | enum index = Idx; 291 | 292 | alias ReturnType = RT!symbol; 293 | alias parameters = Parameters!F; 294 | 295 | string toString() @safe pure { 296 | import std.conv: text; 297 | import std.traits: fullyQualifiedName; 298 | return text(`Function(`, fullyQualifiedName!symbol, ", ", protection, ", ", linkage, ")"); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /source/mirror/meta/traits.d: -------------------------------------------------------------------------------- 1 | /** 2 | Information about types and symbols at compile-time, 3 | similar to std.traits. 4 | */ 5 | module mirror.meta.traits; 6 | 7 | 8 | import mirror.trait_enums: Protection; 9 | static import std.traits; 10 | 11 | 12 | /// Usable as a predicate to std.meta.Filter 13 | enum isEnum(T) = is(T == enum); 14 | 15 | /// Usable as a predicate to std.meta.Filter 16 | enum isStruct(T) = is(T == struct); 17 | 18 | /// Usable as a predicate to std.meta.Filter 19 | enum isInterface(T) = is(T == interface); 20 | 21 | /// Usable as a predicate to std.meta.Filter 22 | enum isClass(T) = is(T == class); 23 | 24 | /// Usable as a predicate to std.meta.Filter 25 | enum isUnion(T) = is(T == union); 26 | 27 | /** 28 | If a type is a class or an interface. 29 | Usable as a predicate to std.meta.Filter 30 | */ 31 | enum isOOP(alias T) = is(T == class) || is(T == interface); 32 | int add() { return 0; } 33 | 34 | template moduleOf(alias T) { 35 | import std.traits: moduleName; 36 | mixin(`import `, moduleName!T, `;`); 37 | mixin(`alias moduleOf = `, moduleName!T, `;`); 38 | } 39 | 40 | 41 | template isPrivate(alias symbol) { 42 | // If a module contains an alias to a basic type, e.g. `alias L = long;`, 43 | // then __traits(getProtection, member) fails to compile 44 | static if(__traits(compiles, __traits(getProtection, symbol))) 45 | enum isPrivate = __traits(getProtection, symbol) == "private"; 46 | else 47 | enum isPrivate = true; // if it doesn't compile, treat it as private 48 | } 49 | 50 | 51 | /** 52 | Retrieves the "fundamental type" of a type T. For most types, this 53 | will be exactly the same as T itself. For arrays or pointers, it 54 | removes as many "layers" of array or pointer indirections to get to 55 | the most basic atomic type possible. Examples of inputs and 56 | outputs: 57 | 58 | * T -> T 59 | * T[] -> T 60 | * T[][] -> T 61 | * T* -> T 62 | 63 | */ 64 | template FundamentalType(T) { 65 | 66 | import std.traits: isArray, isPointer, PointerTarget; 67 | import std.range: ElementEncodingType; 68 | 69 | static if(isArray!T) 70 | alias removeOneIndirection = ElementEncodingType!T; 71 | else static if(isPointer!T) 72 | alias removeOneIndirection = PointerTarget!T; 73 | 74 | private enum isArrayOrPointer(U) = isArray!U || isPointer!U; 75 | 76 | static if(isArrayOrPointer!T) { 77 | static if(isArrayOrPointer!removeOneIndirection) 78 | alias FundamentalType = FundamentalType!removeOneIndirection; 79 | else 80 | alias FundamentalType = removeOneIndirection; 81 | } else 82 | alias FundamentalType = T; 83 | } 84 | 85 | 86 | /** 87 | Returns an AliasSeq of all field types of `T`, depth-first 88 | recursively. 89 | */ 90 | alias RecursiveFieldTypes(T) = RecursiveFieldTypesImpl!T; 91 | 92 | 93 | private template RecursiveFieldTypesImpl(T, alreadySeen...) { 94 | 95 | import mirror.meta.traits: isStruct, isClass; 96 | import std.meta: staticMap, AliasSeq, NoDuplicates, Filter, 97 | templateNot, staticIndexOf; 98 | 99 | enum isStructOrClass(U) = isStruct!(FundamentalType!U) || isClass!(FundamentalType!U); 100 | 101 | static if(isStructOrClass!T) { 102 | 103 | // This check is to deal with forward references such as std.variant.This. 104 | // For some reason, checking for __traits(compiles, T.tupleof) always returns 105 | // true, but checking the length actually does what we want. 106 | // See modules.issues.Issue9. 107 | static if(T.tupleof.length) 108 | private alias fields = AliasSeq!(T.tupleof); 109 | else 110 | private alias fields = AliasSeq!(); 111 | 112 | private alias publicFields = Filter!(templateNot!isPrivate, fields); 113 | private alias type(alias symbol) = typeof(symbol); 114 | private alias types = staticMap!(type, fields); 115 | 116 | private template recurse(U) { 117 | 118 | static if(isStructOrClass!U) { 119 | 120 | // only recurse if the type hasn't been seen yet to 121 | // prevent infinite recursion 122 | enum shouldRecurse = staticIndexOf!(U, alreadySeen) == -1; 123 | 124 | static if(shouldRecurse) 125 | alias recurse = AliasSeq!(U, RecursiveFieldTypesImpl!(FundamentalType!U, NoDuplicates!(T, types, alreadySeen))); 126 | else 127 | alias recurse = AliasSeq!(); 128 | } else 129 | alias recurse = U; 130 | } 131 | 132 | alias RecursiveFieldTypesImpl = NoDuplicates!(staticMap!(recurse, types)); 133 | } else 134 | alias RecursiveFieldTypesImpl = T; 135 | } 136 | 137 | 138 | /** 139 | An std.meta.AliasSeq of `T` and all its recursive 140 | subtypes. 141 | */ 142 | template RecursiveTypeTree(T...) { 143 | import std.meta: staticMap, NoDuplicates; 144 | alias RecursiveTypeTree = NoDuplicates!(T, staticMap!(RecursiveFieldTypes, T)); 145 | } 146 | 147 | 148 | /** 149 | Whether or not `F` is a property function 150 | */ 151 | template isProperty(alias F) { 152 | import std.traits: functionAttributes, FunctionAttribute; 153 | enum isProperty = functionAttributes!F & FunctionAttribute.property; 154 | } 155 | 156 | 157 | /** 158 | All member function symbols in T with overloads represented 159 | separately. "Returns" D symbols, not templates from mirror. 160 | */ 161 | template MemberFunctionsByOverload(T) if(isStruct!T || isClass!T || isInterface!T || isUnion!T) 162 | { 163 | import mirror.meta.reflection: FunctionsByOverload; 164 | import mirror.trait_enums: Protection; 165 | import std.meta: Filter, staticMap; 166 | 167 | private enum isPublic(alias F) = F.protection != Protection.private_; 168 | private alias symbolOf(alias S) = S.symbol; 169 | 170 | alias overloads = FunctionsByOverload!T; 171 | 172 | alias MemberFunctionsByOverload = 173 | Filter!(isMemberFunction, 174 | staticMap!(symbolOf, 175 | Filter!(isPublic, 176 | FunctionsByOverload!T))); 177 | } 178 | 179 | 180 | // must be a global template 181 | private template isMemberFunction(alias F) { 182 | import std.algorithm: startsWith; 183 | 184 | static if(__traits(compiles, __traits(identifier, F))) { 185 | enum name = __traits(identifier, F); 186 | alias parent = __traits(parent, F); 187 | 188 | static if(isOOP!parent) { 189 | private static bool isWantedFunction(string name) { 190 | import std.algorithm: among; 191 | return 192 | !name.among("toString", "toHash", "opCmp", "opEquals", "factory") 193 | && !name.startsWith("__") 194 | ; 195 | } 196 | } else { 197 | bool isWantedFunction(string name) { return true; } 198 | } 199 | private bool isOperator(string name) { 200 | return name.startsWith("op") && name.length > 2 && name[2] >= 'A'; 201 | } 202 | 203 | enum isOp = isOperator(name); 204 | enum isMemberFunction = isWantedFunction(name) && !isOperator(name); 205 | 206 | } else 207 | enum isMemberFunction = false; 208 | } 209 | 210 | 211 | template PublicMembers(alias A) { 212 | import mirror.meta.traits: isPrivate; 213 | import std.meta: Filter, staticMap, Alias, AliasSeq; 214 | 215 | private alias member(string name) = MemberFromName!(A, name); 216 | private alias members = staticMap!(member, __traits(allMembers, A)); 217 | 218 | // In the `member` template above, if it's not possible to get a member from `A`, 219 | // then the symbol is an empty AliasSeq. An example of such a situation can be 220 | // found in `modules.problems` from the tests directory, where this causes things 221 | // to not compile: `version = OopsVersion;`. 222 | // So we filter out such members. 223 | private enum hasSymbol(alias member) = !is(member == void); 224 | private alias goodMembers = Filter!(hasSymbol, members); 225 | 226 | private enum notPrivate(alias member) = !isPrivate!(member.symbol); 227 | 228 | alias PublicMembers = Filter!(notPrivate, goodMembers); 229 | } 230 | 231 | 232 | template MemberFromName(alias parent, string name) { 233 | import std.meta: Alias; 234 | 235 | enum identifier = name; 236 | 237 | static if(__traits(compiles, Alias!(__traits(getMember, parent, name)))) { 238 | 239 | alias symbol = Alias!(__traits(getMember, parent, name)); 240 | 241 | static if(is(symbol)) 242 | alias Type = symbol; 243 | else static if(is(typeof(symbol))) 244 | alias Type = typeof(symbol); 245 | else 246 | alias Type = void; 247 | 248 | } else 249 | alias symbol = void; 250 | } 251 | 252 | 253 | package template memberIsSomeFunction(alias member) { 254 | import std.traits: isSomeFunction; 255 | enum memberIsSomeFunction = isSomeFunction!(member.symbol); 256 | } 257 | 258 | 259 | package template memberIsRegularFunction(alias member) { 260 | static if(memberIsSomeFunction!member) { 261 | import std.algorithm: startsWith; 262 | enum memberIsRegularFunction = 263 | !member.identifier.startsWith("_sharedStaticCtor") 264 | && !member.identifier.startsWith("_staticCtor") 265 | ; 266 | } else 267 | enum memberIsRegularFunction = false; 268 | } 269 | 270 | 271 | /** 272 | If a function is static member function 273 | */ 274 | template isStaticMemberFunction(alias F) { 275 | import std.traits: hasStaticMember; 276 | 277 | static if(__traits(compiles, hasStaticMember!(__traits(parent, F), __traits(identifier, F)))) 278 | enum isStaticMemberFunction = hasStaticMember!(__traits(parent, F), __traits(identifier, F)); 279 | else 280 | enum isStaticMemberFunction = false; 281 | } 282 | 283 | 284 | /** 285 | An AliasSeq of BinaryOperator structs for type T, one for each binary operator. 286 | */ 287 | template BinaryOperators(T) { 288 | import std.meta: staticMap, Filter, AliasSeq; 289 | import std.traits: hasMember; 290 | 291 | // See https://dlang.org/spec/operatoroverloading.html#binary 292 | private alias overloadable = AliasSeq!( 293 | "+", "-", "*", "/", "%", "^^", "&", 294 | "|", "^", "<<", ">>", ">>>", "~", "in", 295 | ); 296 | 297 | static if(hasMember!(T, "opBinary") || hasMember!(T, "opBinaryRight")) { 298 | 299 | private enum hasOperatorDir(BinOpDir dir, string op) = is(typeof(probeOperator!(T, functionName(dir), op))); 300 | private enum hasOperator(string op) = 301 | hasOperatorDir!(BinOpDir.left, op) 302 | || hasOperatorDir!(BinOpDir.right, op); 303 | 304 | alias ops = Filter!(hasOperator, overloadable); 305 | 306 | template toBinOp(string op) { 307 | enum hasLeft = hasOperatorDir!(BinOpDir.left, op); 308 | enum hasRight = hasOperatorDir!(BinOpDir.right, op); 309 | 310 | static if(hasLeft && hasRight) 311 | enum toBinOp = BinaryOperator(op, BinOpDir.left | BinOpDir.right); 312 | else static if(hasLeft) 313 | enum toBinOp = BinaryOperator(op, BinOpDir.left); 314 | else static if(hasRight) 315 | enum toBinOp = BinaryOperator(op, BinOpDir.right); 316 | else 317 | static assert(false); 318 | } 319 | 320 | alias BinaryOperators = staticMap!(toBinOp, ops); 321 | } else 322 | alias BinaryOperators = AliasSeq!(); 323 | } 324 | 325 | 326 | /** 327 | Tests if T has a template function named `funcName` 328 | with a string template parameter `op`. 329 | */ 330 | private auto probeOperator(T, string funcName, string op)() { 331 | import std.traits: Parameters; 332 | 333 | mixin(`alias func = T.` ~ funcName ~ `;`); 334 | alias P = Parameters!(func!op); 335 | 336 | mixin(`return T.init.` ~ funcName ~ `!op(P.init);`); 337 | } 338 | 339 | 340 | struct BinaryOperator { 341 | string op; 342 | BinOpDir dirs; /// left, right, or both 343 | } 344 | 345 | 346 | enum BinOpDir { 347 | left = 1, 348 | right = 2, 349 | } 350 | 351 | 352 | string functionName(BinOpDir dir) { 353 | final switch(dir) with(BinOpDir) { 354 | case left: return "opBinary"; 355 | case right: return "opBinaryRight"; 356 | } 357 | assert(0); 358 | } 359 | 360 | 361 | template UnaryOperators(T) { 362 | import std.meta: AliasSeq, Filter; 363 | 364 | alias overloadable = AliasSeq!("-", "+", "~", "*", "++", "--"); 365 | enum hasOperator(string op) = is(typeof(probeOperator!(T, "opUnary", op))); 366 | alias UnaryOperators = Filter!(hasOperator, overloadable); 367 | } 368 | 369 | 370 | template AssignOperators(T) { 371 | import std.meta: AliasSeq, Filter; 372 | 373 | // See https://dlang.org/spec/operatoroverloading.html#op-assign 374 | private alias overloadable = AliasSeq!( 375 | "+", "-", "*", "/", "%", "^^", "&", 376 | "|", "^", "<<", ">>", ">>>", "~", 377 | ); 378 | 379 | private enum hasOperator(string op) = is(typeof(probeOperator!(T, "opOpAssign", op))); 380 | alias AssignOperators = Filter!(hasOperator, overloadable); 381 | } 382 | 383 | 384 | template NumDefaultParameters(A...) if(A.length == 1) { 385 | import std.traits: isCallable, ParameterDefaults; 386 | import std.meta: Filter; 387 | 388 | alias F = A[0]; 389 | static assert(isCallable!F); 390 | 391 | private template notVoid(T...) if(T.length == 1) { 392 | enum notVoid = !is(T[0] == void); 393 | } 394 | 395 | enum NumDefaultParameters = Filter!(notVoid, ParameterDefaults!F).length; 396 | } 397 | 398 | 399 | template NumRequiredParameters(A...) if(A.length == 1) { 400 | import std.traits: isCallable, Parameters; 401 | alias F = A[0]; 402 | static assert(isCallable!F); 403 | enum NumRequiredParameters = Parameters!F.length - NumDefaultParameters!F; 404 | } 405 | 406 | 407 | /** 408 | AliasSeq of `Parameter` templates with all information on function `F`'s 409 | parameters. 410 | */ 411 | template Parameters(alias F) { 412 | import mirror.meta.traits: Parameter; 413 | import std.traits: StdParameters = Parameters, 414 | ParameterIdentifierTuple, ParameterDefaults, ParameterStorageClassTuple; 415 | import std.meta: staticMap, aliasSeqOf; 416 | import std.range: iota; 417 | 418 | alias parameter(size_t i) = Parameter!( 419 | StdParameters!F[i], 420 | ParameterDefaults!F[i], 421 | ParameterIdentifierTuple!F[i], 422 | ParameterStorageClassTuple!F[i], 423 | ); 424 | 425 | // When a default value is a function pointer, things get... weird 426 | alias parameterFallback(size_t i) = 427 | Parameter!(StdParameters!F[i], void, ParameterIdentifierTuple!F[i]); 428 | 429 | static if(__traits(compiles, staticMap!(parameter, aliasSeqOf!(StdParameters!F.length.iota)))) 430 | alias Parameters = staticMap!(parameter, aliasSeqOf!(StdParameters!F.length.iota)); 431 | else { 432 | import std.traits: fullyQualifiedName; 433 | pragma(msg, "WARNING: Cannot get parameter defaults for `", fullyQualifiedName!F, "`"); 434 | alias Parameters = staticMap!(parameterFallback, aliasSeqOf!(StdParameters!F.length.iota)); 435 | } 436 | } 437 | 438 | /** 439 | Information on a function's parameter 440 | */ 441 | template Parameter( 442 | T, 443 | alias D, 444 | string I, 445 | std.traits.ParameterStorageClass sc = std.traits.ParameterStorageClass.none) 446 | { 447 | alias Type = T; 448 | alias Default = D; 449 | enum identifier = I; 450 | enum storageClass = sc; 451 | } 452 | 453 | 454 | /** 455 | If the passed in template `T` is `Parameter` 456 | */ 457 | template isParameter(alias T) { 458 | import std.traits: TemplateOf; 459 | enum isParameter = __traits(isSame, TemplateOf!T, Parameter); 460 | } 461 | 462 | 463 | template PublicFieldNames(T) { 464 | import std.meta: Filter, AliasSeq; 465 | import std.traits: FieldNameTuple; 466 | 467 | enum isPublic(string fieldName) = __traits(getProtection, __traits(getMember, T, fieldName)) == "public"; 468 | alias PublicFieldNames = Filter!(isPublic, FieldNameTuple!T); 469 | } 470 | 471 | 472 | template isMutableSymbol(alias symbol) { 473 | import std.traits: isMutable; 474 | 475 | static if(isMutable!(typeof(symbol))) { 476 | enum isMutableSymbol = __traits(compiles, symbol = symbol.init); 477 | } else 478 | enum isMutableSymbol = false; 479 | } 480 | 481 | 482 | template isVariable(alias member) { 483 | 484 | enum isVariable = 485 | is(typeof(member.symbol)) 486 | && !is(typeof(member.symbol) == function) 487 | && !is(typeof(member.symbol) == void) // can happen with templates 488 | && is(typeof(member.symbol.init)) 489 | ; 490 | } 491 | 492 | 493 | /** 494 | The fields of a struct, union, or class 495 | */ 496 | template Fields(T) { 497 | import mirror.trait_enums: toProtection; 498 | import std.meta: staticMap, aliasSeqOf, Filter; 499 | import std.traits: FieldTypeTuple, FieldNameTuple; 500 | import std.range: iota; 501 | 502 | private static struct NoType{} 503 | 504 | private alias member(string name) = __traits(getMember, T, name); 505 | 506 | template TypeOf(alias A) { 507 | static if(is(typeof(A))) 508 | alias TypeOf = typeof(A); 509 | else 510 | alias TypeOf = NoType; 511 | } 512 | 513 | enum isFunction(string name) = is(TypeOf!(member!name) == function); 514 | enum hasType(string name) = !is(TypeOf!(member!name) == NoType); 515 | enum isField(string name) = !isFunction!name && hasType!name; 516 | alias fieldNames = Filter!(isField, __traits(allMembers, T)); 517 | alias toField(string name) = Field!( 518 | TypeOf!(member!name), 519 | name, 520 | __traits(getProtection, member!name).toProtection 521 | ); 522 | 523 | alias Fields = staticMap!(toField, fieldNames); 524 | } 525 | 526 | 527 | /** 528 | A field of a struct, union, or class 529 | */ 530 | template Field(F, string id, Protection prot = Protection.public_) { 531 | alias Type = F; 532 | enum identifier = id; 533 | enum protection = prot; 534 | } 535 | -------------------------------------------------------------------------------- /source/mirror/package.d: -------------------------------------------------------------------------------- 1 | module mirror; 2 | 3 | 4 | public import mirror.ctfe.reflection; 5 | public import mirror.meta.reflection; 6 | -------------------------------------------------------------------------------- /source/mirror/rtti.d: -------------------------------------------------------------------------------- 1 | /** 2 | Runtime type information extraced from compile-tine. 3 | */ 4 | module mirror.rtti; 5 | 6 | 7 | /** 8 | Extend runtime type information for the given types. 9 | */ 10 | Types types(T...)() { 11 | 12 | auto ret = Types(); 13 | 14 | static foreach(Type; T) { 15 | ret._typeToInfo[typeid(Type)] = runtimeTypeInfo!Type; 16 | } 17 | 18 | return ret; 19 | } 20 | 21 | 22 | RuntimeTypeInfo runtimeTypeInfo(T)() { 23 | 24 | import mirror.meta.traits: Fields, MemberFunctionsByOverload; 25 | 26 | auto ret = new RuntimeTypeInfoImpl!T(); 27 | 28 | ret.typeInfo = typeid(T); 29 | ret.name = ret.typeInfo.toString; 30 | 31 | static if(is(T == class)) { 32 | 33 | static foreach(field; Fields!T) { 34 | ret.fields ~= new FieldImpl!(T, field.Type, field.identifier) 35 | (typeid(field.Type), field.protection); 36 | } 37 | 38 | static foreach(memberFunction; MemberFunctionsByOverload!T) { 39 | ret.methods ~= new MethodImpl!memberFunction(); 40 | } 41 | } 42 | 43 | return ret; 44 | } 45 | 46 | 47 | /** 48 | Maps types or instances of them to their runtime 49 | type information. 50 | */ 51 | struct Types { 52 | 53 | private RuntimeTypeInfo[TypeInfo] _typeToInfo; 54 | 55 | inout(RuntimeTypeInfo) rtti(T)() inout { 56 | return rtti(typeid(T)); 57 | } 58 | 59 | inout(RuntimeTypeInfo) rtti(T)(auto ref T obj) inout { 60 | import std.traits: isPointer; 61 | 62 | static if(is(T == super)) { 63 | if(obj is null) 64 | throw new Exception("Cannot get RTTI from null object"); 65 | } 66 | 67 | return rtti(typeid(obj)); 68 | } 69 | 70 | inout(RuntimeTypeInfo) rtti(scope TypeInfo typeInfo) @safe scope inout { 71 | scope ptr = typeInfo in _typeToInfo; 72 | 73 | if(ptr is null) { 74 | // TypeInfo.toString isn't scope, so @trusted 75 | scope infoString = () @trusted { return typeInfo.toString; }(); 76 | throw new Exception("Cannot get RTTI for unregistered type " ~ infoString); 77 | } 78 | 79 | return *ptr; 80 | } 81 | } 82 | 83 | 84 | abstract class RuntimeTypeInfo { 85 | 86 | TypeInfo typeInfo; 87 | string name; 88 | Field[] fields; 89 | Method[] methods; 90 | 91 | abstract string toString(in Object obj) @safe pure scope const; 92 | 93 | final inout(Field) field(in string identifier) @safe pure scope inout { 94 | return findInArray(identifier, "field", fields); 95 | } 96 | 97 | final inout(Method) method(in string identifier) @safe pure scope inout { 98 | return findInArray(identifier, "method", methods); 99 | } 100 | 101 | private static findInArray(T)(in string identifier, in string kind, T arr) { 102 | import std.array: empty, front; 103 | import std.algorithm.searching: find; 104 | 105 | auto ret = arr.find!(a => a.identifier == identifier); 106 | 107 | if(ret.empty) 108 | throw new Exception("No " ~ kind ~ " named '" ~ identifier ~ "'"); 109 | 110 | return ret.front; 111 | } 112 | } 113 | 114 | 115 | private class RuntimeTypeInfoImpl(T): RuntimeTypeInfo { 116 | 117 | override string toString(in Object obj) @safe pure scope const { 118 | import std.conv: text; 119 | import std.traits: fullyQualifiedName; 120 | 121 | static if(is(T == class)) { 122 | scope rightType = cast(const T) obj; 123 | if(rightType is null) 124 | throw new Exception("Cannot call toString on obj since not of type " ~ fullyQualifiedName!T); 125 | return text(cast(const T) obj); 126 | } else 127 | throw new Exception("Cannot cast non-class type " ~ fullyQualifiedName!T); 128 | } 129 | } 130 | 131 | 132 | /** 133 | Fields of a struct/class 134 | */ 135 | abstract class Field { 136 | 137 | import mirror.trait_enums: Protection; 138 | import std.variant: Variant; 139 | 140 | const RuntimeTypeInfo type; 141 | immutable string identifier; 142 | immutable Protection protection; 143 | 144 | this(const RuntimeTypeInfo type, string identifier, in Protection protection) @safe pure scope { 145 | this.type = type; 146 | this.identifier = identifier; 147 | this.protection = protection; 148 | } 149 | 150 | final get(T, O)(O obj) const { 151 | import std.traits: CopyTypeQualifiers, fullyQualifiedName; 152 | 153 | auto variant = getImpl(obj); 154 | scope ptr = () @trusted { return variant.peek!T; }(); 155 | 156 | if(ptr is null) 157 | throw new Exception("Cannot get!(" ~ fullyQualifiedName!T ~ ") because of actual type " ~ variant.type.toString); 158 | 159 | return cast(CopyTypeQualifiers!(O, T)) *ptr; 160 | } 161 | 162 | final void set(T)(Object obj, T value) const { 163 | setImpl(obj, () @trusted { return Variant(value); }()); 164 | } 165 | 166 | abstract inout(Variant) getImpl(inout Object obj) @safe const; 167 | abstract void setImpl(Object obj, in Variant value) @safe const; 168 | abstract string toString(in Object obj) @safe const; 169 | } 170 | 171 | 172 | private class FieldImpl(P, F, string member): Field { 173 | 174 | import std.variant: Variant; 175 | 176 | this(TypeInfo typeInfo, in Protection protection) { 177 | import std.traits: fullyQualifiedName; 178 | super(runtimeTypeInfo!F, member, protection); 179 | } 180 | 181 | override inout(Variant) getImpl(inout Object obj) @safe const { 182 | import std.traits: Unqual; 183 | 184 | auto member = getMember(obj); 185 | auto ret = () @trusted { 186 | return Variant(cast(Unqual!(typeof(member))) member); 187 | }(); 188 | 189 | return ret; 190 | } 191 | 192 | override void setImpl(Object obj, in Variant value) @safe const { 193 | import std.traits: fullyQualifiedName; 194 | 195 | static if(is(F == immutable)) 196 | throw new Exception("Cannot set immutable member '" ~ identifier ~ "'"); 197 | else static if(is(F == const)) 198 | throw new Exception("Cannot set const member '" ~ identifier ~ "'"); 199 | else { 200 | 201 | auto ptr = () @trusted { return value.peek!F; }(); 202 | if(ptr is null) 203 | throw new Exception("Cannot set value since not of type " ~ fullyQualifiedName!F); 204 | 205 | getMember(obj) = *ptr; 206 | } 207 | } 208 | 209 | override string toString(in Object obj) @safe const { 210 | import std.conv: text; 211 | return get!F(obj).text; 212 | } 213 | 214 | private: 215 | 216 | ref getMember(O)(O obj) const { 217 | 218 | import mirror.trait_enums: Protection; 219 | import std.traits: Unqual, fullyQualifiedName, CopyTypeQualifiers; 220 | import std.algorithm: among; 221 | 222 | if(!protection.among(Protection.export_, Protection.public_)) 223 | throw new Exception("Cannot get private member"); 224 | 225 | auto rightType = cast(CopyTypeQualifiers!(O, P)) obj; 226 | if(rightType is null) 227 | throw new Exception( 228 | "Cannot call get!" ~ 229 | fullyQualifiedName!F ~ " since not of type " ~ 230 | fullyQualifiedName!P); 231 | 232 | return __traits(getMember, rightType, member); 233 | } 234 | } 235 | 236 | abstract class Method { 237 | 238 | import std.variant: Variant; 239 | 240 | enum TypeQualifier { 241 | mutable, 242 | const_, 243 | immutable_, 244 | } 245 | 246 | immutable string identifier; 247 | const RuntimeTypeInfo type; 248 | 249 | this(string identifier, const RuntimeTypeInfo type) @safe @nogc pure scope const { 250 | this.identifier = identifier; 251 | this.type = type; 252 | } 253 | 254 | final override string toString() @safe pure scope const { 255 | return reprImpl(); 256 | } 257 | 258 | final R call(R = void, O, A...)(O obj, A args) const { 259 | Variant[A.length] variants; 260 | static foreach(i; 0 .. A.length) variants[i] = args[i]; 261 | 262 | static if(is(O == immutable)) 263 | const qualifier = TypeQualifier.immutable_; 264 | else static if(is(O == const)) 265 | const qualifier = TypeQualifier.const_; 266 | else 267 | const qualifier = TypeQualifier.mutable; 268 | 269 | auto impl() { 270 | return callImpl(qualifier, obj, variants[]); 271 | } 272 | 273 | static if(is(R == void)) 274 | impl; 275 | else 276 | return impl.get!R; 277 | } 278 | 279 | final bool isVirtual() @safe @nogc pure scope const { 280 | return !isFinal && !isStatic; 281 | } 282 | 283 | abstract size_t arity() @safe @nogc pure scope const; 284 | abstract bool isFinal() @safe @nogc pure scope const; 285 | abstract bool isOverride() @safe @nogc pure scope const; 286 | abstract bool isStatic() @safe @nogc pure scope const; 287 | abstract bool isSafe() @safe @nogc pure scope const; 288 | abstract RuntimeTypeInfo returnType() @safe scope const; 289 | abstract RuntimeTypeInfo[] parameters() @safe scope const; 290 | abstract string reprImpl() @safe pure scope const; 291 | abstract Variant callImpl(TypeQualifier objQualifier, inout Object obj, Variant[] args) const; 292 | } 293 | 294 | 295 | class MethodImpl(alias F): Method { 296 | 297 | this() const { 298 | super(__traits(identifier, F), runtimeTypeInfo!(typeof(F))); 299 | } 300 | 301 | override string reprImpl() @safe pure scope const { 302 | import std.traits: ReturnType, Parameters; 303 | import std.conv: text; 304 | return text(ReturnType!F.stringof, " ", __traits(identifier, F), Parameters!F.stringof); 305 | } 306 | 307 | override Variant callImpl(TypeQualifier objQualifier, inout Object obj, Variant[] variantArgs) const { 308 | import std.typecons: Tuple; 309 | import std.traits: Parameters, ReturnType, FA = FunctionAttribute, hasFunctionAttributes; 310 | import std.conv: text; 311 | 312 | if(variantArgs.length != Parameters!F.length) 313 | throw new Exception(text("'", identifier, "'", " takes ", 314 | Parameters!F.length, " parameter(s), not ", variantArgs.length)); 315 | 316 | Tuple!(Parameters!F) args; 317 | 318 | alias RightType = __traits(parent, F); 319 | auto rightType = cast(RightType) obj; 320 | 321 | if(rightType is null) 322 | throw new Exception("Cannot call '" ~ identifier ~ "' on object not of type " ~ RightType.stringof); 323 | 324 | const isObjConstant = objQualifier == TypeQualifier.const_ || objQualifier == TypeQualifier.immutable_; 325 | if(isObjConstant && !hasFunctionAttributes!(F, "const")) 326 | throw new Exception("Cannot call non-const method '" ~ identifier ~ "' on const obj"); 327 | 328 | enum mixinStr = `rightType.` ~ __traits(identifier, F) ~ `(args.expand)`; 329 | 330 | static if(__traits(compiles, mixin(mixinStr))) { 331 | 332 | static foreach(i; 0 .. args.length) { 333 | args[i] = variantArgs[i].get!(typeof(args[i])); 334 | } 335 | 336 | static if(is(ReturnType!F == void)) { 337 | mixin(mixinStr, `;`); 338 | return Variant.init; 339 | } else { 340 | auto ret = mixin(mixinStr); 341 | return Variant(ret); 342 | } 343 | } else 344 | throw new Exception("Cannot call " ~ identifier ~ " on object"); 345 | } 346 | 347 | override bool isFinal() @safe @nogc pure scope const { 348 | import std.traits: isFinalFunction; 349 | return isFinalFunction!F; 350 | } 351 | 352 | override bool isOverride() @safe @nogc pure scope const { 353 | return __traits(isOverrideFunction, F); 354 | } 355 | 356 | override bool isStatic() @safe @nogc pure scope const { 357 | return __traits(isStaticFunction, F); 358 | } 359 | 360 | override bool isSafe() @safe @nogc pure scope const { 361 | import std.traits: isSafe; 362 | return isSafe!F; 363 | } 364 | 365 | override size_t arity() @safe @nogc pure scope const { 366 | import std.traits: arity; 367 | return arity!F; 368 | } 369 | 370 | override RuntimeTypeInfo returnType() @safe scope const { 371 | import std.traits: ReturnType; 372 | return runtimeTypeInfo!(ReturnType!F); 373 | } 374 | 375 | override RuntimeTypeInfo[] parameters() @safe scope const { 376 | import std.traits: Parameters; 377 | 378 | RuntimeTypeInfo[] ret; 379 | 380 | static foreach(parameter; Parameters!F) { 381 | ret ~= runtimeTypeInfo!parameter; 382 | } 383 | 384 | return ret; 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /source/mirror/trait_enums.d: -------------------------------------------------------------------------------- 1 | /** 2 | Destringifies traits avaiable from the compiler or std.traits 3 | for getter type safety. 4 | */ 5 | module mirror.trait_enums; 6 | 7 | 8 | /// Visibilty/protection 9 | enum Protection { 10 | private_, 11 | protected_, 12 | public_, 13 | export_, 14 | package_, 15 | } 16 | 17 | 18 | Protection toProtection(in string str) @safe pure { 19 | import std.conv: to; 20 | return (str ~ "_").to!Protection; 21 | } 22 | 23 | 24 | /// 25 | enum Linkage { 26 | D, 27 | C, 28 | Cpp, 29 | Windows, 30 | ObjectiveC, 31 | System, 32 | } 33 | 34 | 35 | Linkage toLinkage(in string str) @safe pure { 36 | import std.conv: to; 37 | if(str == "C++") return Linkage.Cpp; 38 | return str.to!Linkage; 39 | } 40 | -------------------------------------------------------------------------------- /tests/blub.d: -------------------------------------------------------------------------------- 1 | /** 2 | A fictional language implemented in D 3 | */ 4 | module blub; 5 | 6 | 7 | import std.traits: Unqual; 8 | 9 | 10 | struct Blub { 11 | enum Kind { 12 | integer, 13 | string, 14 | } 15 | 16 | Kind kind; 17 | // normally we'd use a union but meh about storage here 18 | private int _integer; 19 | private string _string; 20 | 21 | @disable this(); 22 | 23 | this(int i) @safe @nogc pure nothrow { 24 | kind = Kind.integer; 25 | _integer = i; 26 | } 27 | 28 | this(string s) @safe @nogc pure nothrow { 29 | kind = Kind.string; 30 | _string = s; 31 | } 32 | 33 | int asInteger() @safe @nogc pure const { 34 | if(kind != Kind.integer) throw new Exception("not an int"); 35 | return _integer; 36 | } 37 | 38 | string asString() @safe @nogc pure const { 39 | if(kind != Kind.string) throw new Exception("not a string"); 40 | return _string; 41 | } 42 | } 43 | 44 | 45 | Blub toBlub(int i) { 46 | return Blub(i); 47 | } 48 | 49 | Blub toBlub(string s) { 50 | return Blub(s); 51 | } 52 | 53 | T to(T)(Blub blub) if(is(Unqual!T == int)) { 54 | return blub.asInteger; 55 | } 56 | 57 | T to(T)(Blub blub) if(is(Unqual!T == string)) { 58 | return blub.asString; 59 | } 60 | -------------------------------------------------------------------------------- /tests/main.d: -------------------------------------------------------------------------------- 1 | import unit_threaded; 2 | 3 | 4 | mixin runTestsMain!( 5 | "ut.issues", 6 | "ut.ctfe.reflection.types", 7 | "ut.ctfe.reflection.variables", 8 | "ut.ctfe.reflection.functions", 9 | "ut.ctfe.reflection.wrap", 10 | "ut.ctfe.reflection.extra", 11 | "ut.ctfe.reflection.traits", 12 | "ut.ctfe.reflection.runtime", 13 | "ut.rtti.oop", 14 | "ut.rtti.any", 15 | "ut.meta.traits", 16 | "ut.meta.reflection.types", 17 | "ut.meta.reflection.variables", 18 | "ut.meta.reflection.functions", 19 | ); 20 | -------------------------------------------------------------------------------- /tests/modules/empty.d: -------------------------------------------------------------------------------- 1 | module modules.empty; 2 | -------------------------------------------------------------------------------- /tests/modules/extra.d: -------------------------------------------------------------------------------- 1 | module modules.extra; 2 | 3 | @safe pure unittest {} 4 | @safe pure unittest { throw new Exception("oh noes"); } 5 | 6 | struct Struct { 7 | @safe pure unittest { throw new Exception("oh noes from struct"); } 8 | } 9 | -------------------------------------------------------------------------------- /tests/modules/functions.d: -------------------------------------------------------------------------------- 1 | module modules.functions; 2 | static import modules.templates; 3 | 4 | 5 | int addd(int i, int j) @safe @nogc pure nothrow { 6 | return i + j + 1; 7 | } 8 | 9 | 10 | double addd(double d0, double d1) @safe @nogc pure nothrow { 11 | return d0 + d1 + 2; // do the wrong thing on purpose 12 | } 13 | 14 | 15 | double withDefault(double fst, double snd = 33.3) { 16 | return fst + snd; 17 | } 18 | 19 | 20 | void storageClasses( 21 | int normal, 22 | return scope int* returnScope, 23 | out int out_, 24 | ref int ref_, 25 | lazy int lazy_, 26 | ) 27 | { 28 | 29 | } 30 | 31 | 32 | export void exportedFunc() {} 33 | 34 | extern(C) void externC() {} 35 | 36 | extern(C++) void externCpp() {} 37 | 38 | 39 | alias identityInt = modules.templates.identity!int; 40 | 41 | 42 | shared static this() { } 43 | 44 | static this() { } 45 | 46 | 47 | unittest {} 48 | 49 | 50 | auto voldemort(int i) { 51 | static struct Voldemort { 52 | int i; 53 | } 54 | 55 | return Voldemort(i); 56 | } 57 | 58 | 59 | auto voldemortArray(int i) { 60 | 61 | static struct DasVoldemort { 62 | int i; 63 | } 64 | 65 | return [DasVoldemort(i)]; 66 | } 67 | 68 | 69 | string concatFoo(string s0, int i, string s1) { 70 | import std.conv: text; 71 | return s0 ~ i.text ~ s1 ~ "foo"; 72 | } 73 | -------------------------------------------------------------------------------- /tests/modules/imports.d: -------------------------------------------------------------------------------- 1 | module modules.imports; 2 | 3 | // Import a few modules so that the reflection code has to deal with 4 | // the imported symbols. 5 | import modules.functions; 6 | import std.system; 7 | import std.demangle; 8 | import core.thread; 9 | -------------------------------------------------------------------------------- /tests/modules/issues.d: -------------------------------------------------------------------------------- 1 | module modules.issues; 2 | 3 | 4 | struct Issue1 { 5 | static struct String { string value; } 6 | size_t length(String str) { return str.value.length; } 7 | } 8 | 9 | 10 | struct CtorProtectionsStruct { 11 | // the public one must be first so that it's the default symbol if anyone 12 | // tries to reflect on "__ctor" 13 | public this(double d) {} 14 | private this(int i, string s) {} 15 | package this(string s, int i) {} 16 | } 17 | 18 | 19 | struct Issue9; 20 | -------------------------------------------------------------------------------- /tests/modules/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Modules that exisit solely for testing purposes to be reflected on. 3 | */ 4 | module modules; 5 | -------------------------------------------------------------------------------- /tests/modules/problems.d: -------------------------------------------------------------------------------- 1 | module modules.problems; 2 | 3 | 4 | private int gInt; 5 | 6 | private struct Struct {} 7 | private class Class {} 8 | private enum Enum { foo = 0 } 9 | 10 | 11 | private int sub1(int i, int j) { 12 | return i - j - 1; 13 | } 14 | 15 | 16 | alias MyLong = long; 17 | 18 | 19 | version = OopsVersion; 20 | 21 | 22 | int[] gInts; 23 | 24 | public struct PrivateFields { 25 | private int i; 26 | public string s; 27 | } 28 | -------------------------------------------------------------------------------- /tests/modules/runtime.d: -------------------------------------------------------------------------------- 1 | module modules.runtime; 2 | 3 | 4 | import mirror; 5 | 6 | 7 | mixin registerModule!(); 8 | 9 | 10 | int twice(int i) @safe @nogc pure nothrow { 11 | return i * 2; 12 | } 13 | 14 | int mul(int i, int j) @safe @nogc pure nothrow { 15 | return i * j; 16 | } 17 | -------------------------------------------------------------------------------- /tests/modules/templates.d: -------------------------------------------------------------------------------- 1 | module modules.templates; 2 | 3 | T identity(T)(T x) { return x; } 4 | -------------------------------------------------------------------------------- /tests/modules/traits.d: -------------------------------------------------------------------------------- 1 | module modules.traits; 2 | 3 | struct Struct { 4 | @disable void disabled() {} 5 | int notDisabled() @safe @nogc pure nothrow const { return 42; } 6 | static struct ReturnStruct { 7 | int[20] ints; 8 | } 9 | ReturnStruct returnStruct() { return ReturnStruct(); } 10 | import core.stdc.stdarg; // annoying editor error otherwise 11 | extern(C) void stdarg(int, ...) {} 12 | void argptr(...) {} 13 | void typesafe(int[]...) {} 14 | } 15 | 16 | class Class: Base { 17 | void foo() {} 18 | void bar() {} 19 | abstract void abstract_(); 20 | final void final_() {} 21 | override void overrideThis() {} 22 | static void static_() {} 23 | } 24 | 25 | class Base { 26 | void overrideThis() {} 27 | } 28 | 29 | @__future int theFuture; 30 | deprecated("cos I said so") int theDeprecated; 31 | 32 | enum modulesTraitsFile = __FILE__; 33 | 34 | @(42, "a string", Struct(), Struct, Class, twice, typeof(&twice)) 35 | int intWithUDAs; 36 | 37 | int twice(int i) { 38 | return i * 2; 39 | } 40 | -------------------------------------------------------------------------------- /tests/modules/types.d: -------------------------------------------------------------------------------- 1 | module modules.types; 2 | 3 | 4 | struct String { 5 | string value; 6 | 7 | string withPrefix() @safe pure nothrow scope const { 8 | return "pre_" ~ value; 9 | } 10 | 11 | string withPrefix(in string prefix) @safe pure nothrow scope const { 12 | return prefix ~ value; 13 | } 14 | } 15 | 16 | 17 | enum Enum { 18 | foo, 19 | bar, 20 | baz, 21 | } 22 | 23 | class Class { 24 | int i; 25 | this(int i) { this.i = i; } 26 | } 27 | 28 | 29 | interface Interface { 30 | int foo(double d, string s); 31 | } 32 | 33 | class AbstractClass { 34 | abstract double bar(int i); 35 | } 36 | 37 | class MiddleClass: AbstractClass { 38 | abstract string baz(string s); 39 | } 40 | 41 | class LeafClass: MiddleClass, Interface { 42 | override int foo(double d, string s) { 43 | return 42; 44 | } 45 | 46 | override double bar(int i) { 47 | return i * 2; 48 | } 49 | 50 | override string baz(string s) { 51 | return s ~ "_baz"; 52 | } 53 | } 54 | 55 | 56 | // just to test this gets ignored 57 | int func(string s, double d) { 58 | return 42; 59 | } 60 | 61 | 62 | struct Point { 63 | double x, y; 64 | } 65 | 66 | 67 | struct Inner1 { 68 | Point point; 69 | double value; 70 | } 71 | 72 | 73 | struct EvenInner { 74 | double value; 75 | } 76 | 77 | 78 | struct Inner2 { 79 | EvenInner evenInner; 80 | } 81 | 82 | 83 | struct Outer { 84 | Inner1[] inner1s; 85 | Inner2 inner2; 86 | } 87 | 88 | 89 | enum Char: char { 90 | a = 'a', 91 | b = 'b', 92 | } 93 | 94 | union Union { 95 | 96 | } 97 | 98 | struct RussianDoll { 99 | struct Mid { 100 | struct Inner { 101 | int i; 102 | int twice() @safe @nogc pure nothrow scope const { 103 | return i * 2; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/modules/variables.d: -------------------------------------------------------------------------------- 1 | module modules.variables; 2 | 3 | int gInt; 4 | immutable gDouble = 33.3; 5 | 6 | struct Struct {} 7 | 8 | Struct gStruct; 9 | 10 | enum CONSTANT_INT = 42; 11 | enum CONSTANT_STRING = "foobar"; 12 | 13 | immutable int gImmutableInt = 77; 14 | 15 | 16 | // just to check that this doesn't cause problems 17 | auto templateFunction(T...)(T stuff) { 18 | ubyte[16] ret; 19 | return ret; 20 | } 21 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/extra.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.extra; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("unitTests") 8 | @safe pure unittest { 9 | import std.algorithm: map; 10 | 11 | static immutable mod = module_!"modules.extra"(); 12 | 13 | mod.unitTests.map!(a => a.fullyQualifiedName).should == [ 14 | "modules.extra.__unittest_L3_C12", 15 | "modules.extra.__unittest_L4_C12", 16 | ]; 17 | 18 | static immutable failingUtInfo = mod.unitTests[1]; 19 | mixin(failingUtInfo.importMixin); 20 | alias failingUt = mixin(failingUtInfo.aliasMixin); 21 | failingUt().shouldThrowWithMessage("oh noes"); 22 | 23 | mod.aggregates[0].unitTests.map!(a => a.fullyQualifiedName).should == [ 24 | "modules.extra.Struct.__unittest_L7_C16", 25 | ]; 26 | static immutable structUtInfo = mod.aggregates[0].unitTests[0]; 27 | alias structUt = mixin(structUtInfo.aliasMixin); 28 | structUt.shouldThrowWithMessage("oh noes from struct"); 29 | } 30 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/functions.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.functions; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | import std.traits: PSC = ParameterStorageClass; 6 | 7 | 8 | @("problems") 9 | @safe pure unittest { 10 | // just to check there are no compilation errors 11 | static immutable mod = module_!"modules.problems"(); 12 | } 13 | 14 | 15 | @("functionsByOverload.call.ct.addd.0") 16 | @safe pure unittest { 17 | static immutable mod = module_!"modules.functions"(); 18 | static immutable addd_0 = mod.functionsByOverload[0]; 19 | 20 | mixin(addd_0.importMixin); 21 | alias addd_0Sym = mixin(addd_0.aliasMixin); 22 | static assert(is(typeof(&addd_0Sym) == int function(int, int) @safe @nogc pure nothrow)); 23 | 24 | addd_0Sym(1, 2).should == 4; 25 | addd_0Sym(2, 3).should == 6; 26 | } 27 | 28 | @("functionsByOverload.call.ct.addd.1") 29 | @safe pure unittest { 30 | static immutable mod = module_!"modules.functions"(); 31 | static immutable addd_1 = mod.functionsByOverload[1]; 32 | 33 | mixin(addd_1.importMixin); 34 | alias addd_1Sym = mixin(addd_1.aliasMixin); 35 | static assert(is(typeof(&addd_1Sym) == double function(double, double) @safe @nogc pure nothrow)); 36 | 37 | addd_1Sym(1, 2).should == 5; 38 | addd_1Sym(2, 3).should == 7; 39 | } 40 | 41 | @("functionsByOverload.call.rt.opCall.addd.0") 42 | @system unittest { 43 | import std.variant: Variant; 44 | 45 | const mod = module_!"modules.functions"(); 46 | const addd_0 = mod.functionsByOverload[0]; 47 | 48 | addd_0([Variant(1), Variant(2)]).get!int.should == 4; 49 | addd_0([Variant(2), Variant(3)]).get!int.should == 6; 50 | } 51 | 52 | @("functionsByOverload.call.rt.call.ok.addd.0") 53 | @system unittest { 54 | import std.variant: Variant; 55 | 56 | const mod = module_!"modules.functions"(); 57 | const addd_0 = mod.functionsByOverload[0]; 58 | 59 | addd_0.funCall!int(1, 2).should == 4; 60 | addd_0.funCall!int(2, 3).should == 6; 61 | } 62 | 63 | @("functionsByOverload.call.rt.call.oops.addd.0") 64 | @system unittest { 65 | import std.variant: Variant; 66 | 67 | const mod = module_!"modules.functions"(); 68 | const addd_0 = mod.functionsByOverload[0]; 69 | 70 | addd_0.funCall!int(1, 2, 3).shouldThrowWithMessage( 71 | "Cannot call `modules.functions.addd` with 3 arguments. Expected: 2"); 72 | 73 | addd_0.funCall!int(1, "foo").shouldThrowWithMessage( 74 | "Expected argument #1 of `modules.functions.addd` to be `int`, got: `foo`"); 75 | } 76 | 77 | 78 | @("functionsBySymbol.call.addd") 79 | @safe pure unittest { 80 | static immutable mod = module_!"modules.functions"(); 81 | static immutable addd = mod.functionsBySymbol[0]; 82 | static assert(addd.identifier == "addd"); 83 | 84 | mixin(addd.importMixin); 85 | alias addd_0Sym = mixin(addd.overloads[0].aliasMixin); 86 | alias addd_1Sym = mixin(addd.overloads[1].aliasMixin); 87 | 88 | static assert(is(typeof(&addd_0Sym) == int function(int, int) @safe @nogc pure nothrow)); 89 | static assert(is(typeof(&addd_1Sym) == double function(double, double) @safe @nogc pure nothrow)); 90 | 91 | addd_1Sym(1, 2).should == 5; 92 | addd_1Sym(2, 3).should == 7; 93 | } 94 | 95 | 96 | @("visibility.public") 97 | @safe pure unittest { 98 | static immutable mod = module_!"modules.functions"(); 99 | static immutable func = mod.functionsByOverload[0]; 100 | func.visibility.should == Visibility.public_; 101 | } 102 | 103 | @("visibility.export") 104 | @safe pure unittest { 105 | static immutable mod = module_!"modules.functions"(); 106 | static immutable func = mod.functionsByOverload[4]; 107 | func.visibility.should == Visibility.export_; 108 | } 109 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/package.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection; 2 | 3 | 4 | public import ut; 5 | public import mirror.ctfe.reflection; 6 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/runtime.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.runtime; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("registerModule.runtime") 8 | @safe pure unittest { 9 | import modules.runtime: rtModuleInfo = gModuleInfo; 10 | static immutable ctModuleInfo = module_!"modules.runtime"; 11 | rtModuleInfo.fullyQualifiedName.should == ctModuleInfo.fullyQualifiedName; 12 | rtModuleInfo.functionsBySymbol.length.should == ctModuleInfo.functionsBySymbol.length; 13 | } 14 | 15 | @("moduleInfos") 16 | @safe unittest { // not pure because accessing a global 17 | allModuleInfos.length.should == 1; 18 | allModuleInfos[0].fullyQualifiedName.should == "modules.runtime"; 19 | } 20 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/traits.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.traits; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("isArithmetic") 8 | @safe pure unittest { 9 | type!int.isArithmetic.should == true; 10 | type!float.isArithmetic.should == true; 11 | static struct Struct {} 12 | type!Struct.isArithmetic.should == false; 13 | } 14 | 15 | @("isFloating") 16 | @safe pure unittest { 17 | type!int.isFloating.should == false; 18 | type!float.isFloating.should == true; 19 | } 20 | 21 | @("isIntegral") 22 | @safe pure unittest { 23 | type!int.isIntegral.should == true; 24 | type!float.isIntegral.should == false; 25 | } 26 | 27 | @("isScalar") 28 | @safe pure unittest { 29 | type!int.isScalar.should == true; 30 | type!(int[4]).isScalar.should == false; 31 | } 32 | 33 | @("isUnsigned") 34 | @safe pure unittest { 35 | type!int.isUnsigned.should == false; 36 | type!uint.isUnsigned.should == true; 37 | } 38 | 39 | @("isStaticArray") 40 | @safe pure unittest { 41 | type!int.isStaticArray.should == false; 42 | type!(int[4]).isStaticArray.should == true; 43 | } 44 | 45 | @("isAssociativeArray") 46 | @safe pure unittest { 47 | type!int.isAssociativeArray.should == false; 48 | type!(int[string]).isAssociativeArray.should == true; 49 | } 50 | 51 | @("isAbstractClass") 52 | @safe pure unittest { 53 | type!int.isAbstractClass.should == false; 54 | static abstract class C {} 55 | type!C.isAbstractClass.should == true; 56 | } 57 | 58 | @("isFinalClass") 59 | @safe pure unittest { 60 | type!int.isFinalClass.should == false; 61 | static final class C {} 62 | type!C.isFinalClass.should == true; 63 | } 64 | 65 | @("isCopyable") 66 | @safe pure unittest { 67 | type!int.isCopyable.should == true; 68 | static struct S { @disable this(this); } 69 | type!S.isCopyable.should == false; 70 | } 71 | 72 | @("isPOD") 73 | @safe pure unittest { 74 | type!int.isPOD.should == true; 75 | static struct S { ~this() { } } 76 | type!S.isPOD.should== false; 77 | } 78 | 79 | @("isZeroInit") 80 | @safe pure unittest { 81 | type!int.isZeroInit.should == true; 82 | type!char.isZeroInit.should == false; 83 | } 84 | 85 | @("hasCopyConstructor") 86 | @safe pure unittest { 87 | type!int.hasCopyConstructor.should == false; 88 | struct S { this(ref const(S)) {} } 89 | type!S.hasCopyConstructor.should == true; 90 | } 91 | 92 | @ShouldFail 93 | @("hasMoveConstructor") 94 | @safe pure unittest { 95 | type!int.hasMoveConstructor.should == false; 96 | struct S { this(S s) {} } 97 | type!S.hasMoveConstructor.should == true; 98 | } 99 | 100 | @("hasPostblit") 101 | @safe pure unittest { 102 | type!int.hasPostblit.should == false; 103 | struct S { this(this) {} } 104 | type!S.hasPostblit.should == true; 105 | } 106 | 107 | @("aliasThis") 108 | @safe pure unittest { 109 | type!int.aliasThis.shouldBeEmpty; 110 | struct S { 111 | string var; 112 | alias var this; 113 | } 114 | type!S.aliasThis.should == ["var"]; 115 | } 116 | 117 | @("pointerBitmap") 118 | @safe pure unittest { 119 | type!int.pointerBitmap.should == [int.sizeof, 0]; 120 | struct S { 121 | long i; 122 | string s; 123 | } 124 | type!S.pointerBitmap.should == [S.sizeof, 4]; 125 | } 126 | 127 | @("classInstanceSize") 128 | @safe pure unittest { 129 | type!int.classInstanceSize.should == 0; 130 | class C { 131 | int i; 132 | string s; 133 | } 134 | type!C.classInstanceSize.should == 48; 135 | } 136 | 137 | @("classInstanceAlignment") 138 | @safe pure unittest { 139 | type!int.classInstanceAlignment.should == 0; 140 | class C { 141 | int i; 142 | string s; 143 | } 144 | type!C.classInstanceAlignment.should == 8; 145 | } 146 | 147 | 148 | @("isDisabled") 149 | @safe pure unittest { 150 | static immutable mod = module_!"modules.traits"(); 151 | const struct_ = mod.aggregates[0]; 152 | const disabled = struct_.functionsByOverload[0]; 153 | disabled.fullyQualifiedName.should == "modules.traits.Struct.disabled"; 154 | disabled.isDisabled.should == true; 155 | const notDisabled = struct_.functionsByOverload[1]; 156 | notDisabled.fullyQualifiedName.should == "modules.traits.Struct.notDisabled"; 157 | notDisabled.isDisabled.should == false; 158 | } 159 | 160 | @("virtualIndex") 161 | @safe pure unittest { 162 | static immutable mod = module_!"modules.traits"(); 163 | const class_ = mod.aggregates[1]; 164 | const bar = class_.functionsByOverload[1]; 165 | bar.fullyQualifiedName.should == "modules.traits.Class.bar"; 166 | bar.virtualIndex.should == 7; 167 | } 168 | 169 | @("isVirtualMethod") 170 | @safe pure unittest { 171 | static immutable mod = module_!"modules.traits"(); 172 | const struct_ = mod.aggregates[0]; 173 | const disabled = struct_.functionsByOverload[0]; 174 | disabled.fullyQualifiedName.should == "modules.traits.Struct.disabled"; 175 | disabled.isVirtualMethod.should == false; 176 | 177 | const class_ = mod.aggregates[1]; 178 | const bar = class_.functionsByOverload[1]; 179 | bar.fullyQualifiedName.should == "modules.traits.Class.bar"; 180 | bar.isVirtualMethod.should == true; 181 | } 182 | 183 | @("isFinalAbstract") 184 | @safe pure unittest { 185 | static immutable mod = module_!"modules.traits"(); 186 | const class_ = mod.aggregates[1]; 187 | 188 | const abs = class_.functionsByOverload[2]; 189 | abs.fullyQualifiedName.should == "modules.traits.Class.abstract_"; 190 | abs.isAbstract.should == true; 191 | abs.isFinal.should == false; 192 | 193 | const fin = class_.functionsByOverload[3]; 194 | fin.fullyQualifiedName.should == "modules.traits.Class.final_"; 195 | fin.isAbstract.should == false; 196 | fin.isFinal.should == true; 197 | } 198 | 199 | @("isOverride") 200 | @safe pure unittest { 201 | static immutable mod = module_!"modules.traits"(); 202 | const class_ = mod.aggregates[1]; 203 | 204 | const abs = class_.functionsByOverload[2]; 205 | abs.fullyQualifiedName.should == "modules.traits.Class.abstract_"; 206 | abs.isOverride.should == false; 207 | 208 | const ovr = class_.functionsByOverload[4]; 209 | ovr.fullyQualifiedName.should == "modules.traits.Class.overrideThis"; 210 | ovr.isOverride.should == true; 211 | } 212 | 213 | @("isStatic") 214 | @safe pure unittest { 215 | static immutable mod = module_!"modules.traits"(); 216 | const class_ = mod.aggregates[1]; 217 | 218 | const abs = class_.functionsByOverload[2]; 219 | abs.fullyQualifiedName.should == "modules.traits.Class.abstract_"; 220 | abs.isStatic.should == false; 221 | 222 | const stc = class_.functionsByOverload[5]; 223 | stc.fullyQualifiedName.should == "modules.traits.Class.static_"; 224 | stc.isStatic.should == true; 225 | } 226 | 227 | @("isReturnOnStack") 228 | @safe pure unittest { 229 | static immutable mod = module_!"modules.traits"(); 230 | const struct_ = mod.aggregates[0]; 231 | const notDisabled = struct_.functionsByOverload[1]; 232 | notDisabled.fullyQualifiedName.should == "modules.traits.Struct.notDisabled"; 233 | notDisabled.isReturnOnStack.should == false; 234 | 235 | const returnStruct = struct_.functionsByOverload[2]; 236 | returnStruct.fullyQualifiedName.should == "modules.traits.Struct.returnStruct"; 237 | returnStruct.isReturnOnStack.should == true; 238 | } 239 | 240 | @("variadicStyle") 241 | @safe pure unittest { 242 | import mirror.ctfe.reflection: Function; 243 | 244 | static immutable mod = module_!"modules.traits"(); 245 | const struct_ = mod.aggregates[0]; 246 | { 247 | const notDisabled = struct_.functionsByOverload[1]; 248 | notDisabled.fullyQualifiedName.should == "modules.traits.Struct.notDisabled"; 249 | notDisabled.variadicStyle.should == Function.VariadicStyle.none; 250 | } 251 | 252 | { 253 | const stdarg = struct_.functionsByOverload[3]; 254 | stdarg.fullyQualifiedName.should == "modules.traits.Struct.stdarg"; 255 | stdarg.variadicStyle.should == Function.VariadicStyle.stdarg; 256 | } 257 | 258 | { 259 | const argptr = struct_.functionsByOverload[4]; 260 | argptr.fullyQualifiedName.should == "modules.traits.Struct.argptr"; 261 | argptr.variadicStyle.should == Function.VariadicStyle.argptr; 262 | } 263 | 264 | { 265 | const typesafe = struct_.functionsByOverload[5]; 266 | typesafe.fullyQualifiedName.should == "modules.traits.Struct.typesafe"; 267 | typesafe.variadicStyle.should == Function.VariadicStyle.typesafe; 268 | } 269 | } 270 | 271 | @("function.attributes") 272 | @safe pure unittest { 273 | static immutable mod = module_!"modules.traits"(); 274 | const struct_ = mod.aggregates[0]; 275 | { 276 | const disabled = struct_.functionsByOverload[0]; 277 | disabled.fullyQualifiedName.should == "modules.traits.Struct.disabled"; 278 | disabled.attributes.should == ["@system"]; 279 | } 280 | { 281 | const notDisabled = struct_.functionsByOverload[1]; 282 | notDisabled.fullyQualifiedName.should == "modules.traits.Struct.notDisabled"; 283 | notDisabled.attributes.dup.should ~ ["@safe", "@nogc", "pure", "nothrow", "const"]; 284 | } 285 | } 286 | 287 | @("isFuture") 288 | @safe pure unittest { 289 | static immutable mod = module_!"modules.traits"(); 290 | { 291 | const struct_ = mod.aggregates[0]; 292 | struct_.isFuture.should == false; 293 | } 294 | 295 | { 296 | const theFuture = mod.variables[0]; 297 | theFuture.fullyQualifiedName.should == "modules.traits.theFuture"; 298 | theFuture.isFuture.should == true; 299 | } 300 | } 301 | 302 | @("isDeprecated") 303 | @safe pure unittest { 304 | static immutable mod = module_!"modules.traits"(); 305 | { 306 | const struct_ = mod.aggregates[0]; 307 | struct_.isDeprecated.should == false; 308 | } 309 | 310 | { 311 | const theDeprecated = mod.variables[1]; 312 | theDeprecated.fullyQualifiedName.should == "modules.traits.theDeprecated"; 313 | theDeprecated.isDeprecated.should == true; 314 | } 315 | } 316 | 317 | @("isModule") 318 | @safe pure unittest { 319 | static immutable mod = module_!"modules.traits"(); 320 | mod.isModule.should == true; 321 | const struct_ = mod.aggregates[0]; 322 | struct_.isModule.should == false; 323 | } 324 | 325 | @("location") 326 | @safe pure unittest { 327 | import modules.traits: modulesTraitsFile; 328 | static immutable mod = module_!"modules.traits"(); 329 | const struct_ = mod.aggregates[0]; 330 | struct_.location.should == Location(modulesTraitsFile, 3, 1); 331 | } 332 | 333 | @("UDAs") 334 | @trusted /*should == */ unittest { 335 | static import modules.traits; 336 | static immutable mod = module_!"modules.traits"; 337 | const var = mod.variables[3]; 338 | var.fullyQualifiedName.should == "modules.traits.intWithUDAs"; 339 | var.UDAs.should == [ 340 | new ValueUDA(42), 341 | new ValueUDA("a string"), 342 | new ValueUDA(modules.traits.Struct()), 343 | TypeUDA.create!(modules.traits.Struct), 344 | TypeUDA.create!(modules.traits.Class), 345 | SymbolUDA.create!(modules.traits.twice), 346 | TypeUDA.create!(typeof(&modules.traits.twice)), 347 | ]; 348 | } 349 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/types.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.types; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("empty") 8 | @safe pure unittest { 9 | module_!"modules.empty".aggregates.length.should == 0; 10 | } 11 | 12 | @("imports") 13 | @safe pure unittest { 14 | module_!"modules.imports".aggregates.length.should == 0; 15 | } 16 | 17 | @("nameskinds") 18 | @safe pure unittest { 19 | import std.algorithm: map; 20 | 21 | static immutable mod = module_!"modules.types"; 22 | 23 | static struct NameAndKind { 24 | string id; 25 | Aggregate.Kind kind; 26 | } 27 | 28 | static NameAndKind xform(in Aggregate a) { 29 | return NameAndKind(a.fullyQualifiedName, a.kind); 30 | } 31 | 32 | mod.aggregates.map!xform.should == [ 33 | NameAndKind("modules.types.String", Aggregate.Kind.struct_), 34 | NameAndKind("modules.types.Enum", Aggregate.Kind.enum_), 35 | NameAndKind("modules.types.Class", Aggregate.Kind.class_), 36 | NameAndKind("modules.types.Interface", Aggregate.Kind.interface_), 37 | NameAndKind("modules.types.AbstractClass", Aggregate.Kind.class_), 38 | NameAndKind("modules.types.MiddleClass", Aggregate.Kind.class_), 39 | NameAndKind("modules.types.LeafClass", Aggregate.Kind.class_), 40 | NameAndKind("modules.types.Point", Aggregate.Kind.struct_), 41 | NameAndKind("modules.types.Inner1", Aggregate.Kind.struct_), 42 | NameAndKind("modules.types.EvenInner", Aggregate.Kind.struct_), 43 | NameAndKind("modules.types.Inner2", Aggregate.Kind.struct_), 44 | NameAndKind("modules.types.Outer", Aggregate.Kind.struct_), 45 | NameAndKind("modules.types.Char", Aggregate.Kind.enum_), 46 | NameAndKind("modules.types.Union", Aggregate.Kind.union_), 47 | NameAndKind("modules.types.RussianDoll", Aggregate.Kind.struct_), 48 | ]; 49 | 50 | mod.allAggregates.map!xform.should == [ 51 | NameAndKind("modules.types.String", Aggregate.Kind.struct_), 52 | NameAndKind("modules.types.Enum", Aggregate.Kind.enum_), 53 | NameAndKind("modules.types.Class", Aggregate.Kind.class_), 54 | NameAndKind("modules.types.Interface", Aggregate.Kind.interface_), 55 | NameAndKind("modules.types.AbstractClass", Aggregate.Kind.class_), 56 | NameAndKind("modules.types.MiddleClass", Aggregate.Kind.class_), 57 | NameAndKind("modules.types.LeafClass", Aggregate.Kind.class_), 58 | NameAndKind("modules.types.Point", Aggregate.Kind.struct_), 59 | NameAndKind("modules.types.Inner1", Aggregate.Kind.struct_), 60 | NameAndKind("modules.types.EvenInner", Aggregate.Kind.struct_), 61 | NameAndKind("modules.types.Inner2", Aggregate.Kind.struct_), 62 | NameAndKind("modules.types.Outer", Aggregate.Kind.struct_), 63 | NameAndKind("modules.types.Char", Aggregate.Kind.enum_), 64 | NameAndKind("modules.types.Union", Aggregate.Kind.union_), 65 | NameAndKind("modules.types.RussianDoll", Aggregate.Kind.struct_), 66 | ]; 67 | } 68 | 69 | @("problems") 70 | @safe pure unittest { 71 | import std.array: front; 72 | import std.conv: text; 73 | import std.algorithm: find, map; 74 | 75 | static immutable mod = module_!"modules.problems"; 76 | 77 | auto actual = mod.aggregates.find!(a => a.identifier == "PrivateFields")[0]; 78 | actual.fullyQualifiedName.should == "modules.problems.PrivateFields"; 79 | actual.kind.should == Aggregate.Kind.struct_; 80 | actual.variables.map!(v => text(v.fullyQualifiedName, `: `, v.type.fullyQualifiedName)).should == [ 81 | "modules.problems.PrivateFields.i: int", 82 | "modules.problems.PrivateFields.s: string", 83 | ]; 84 | 85 | actual.variables[0].visibility.should == Visibility.private_; 86 | actual.variables[1].visibility.should == Visibility.public_; 87 | } 88 | 89 | 90 | @("fields.String") 91 | @safe pure unittest { 92 | import std.conv: text; 93 | import std.algorithm: map; 94 | 95 | static immutable mod = module_!"modules.types"; 96 | auto string_ = mod.aggregates[0]; 97 | 98 | string_.variables.map!(v => text(v.fullyQualifiedName, `: `, v.type.fullyQualifiedName)).should == [ 99 | "modules.types.String.value: string", 100 | ]; 101 | 102 | string_.variables[0].visibility.should == Visibility.public_; 103 | } 104 | 105 | @("fields.Point") 106 | @safe pure unittest { 107 | import std.algorithm: find, map; 108 | import std.conv: text; 109 | 110 | static immutable mod = module_!"modules.types"; 111 | auto point = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.Point")[0]; 112 | point.variables.map!(v => text(v.fullyQualifiedName, `: `, v.type.fullyQualifiedName)).should == [ 113 | "modules.types.Point.x: double", 114 | "modules.types.Point.y: double", 115 | ]; 116 | } 117 | 118 | 119 | @("methods.String") 120 | @safe pure unittest { 121 | import std.algorithm: find, map; 122 | import std.array: array; 123 | 124 | static immutable mod = module_!"modules.types"; 125 | static immutable str = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.String")[0]; 126 | str.functionsByOverload.map!(a => a.identifier).array.should == ["withPrefix", "withPrefix"]; 127 | 128 | static immutable withPrefix0Info = str.functionsByOverload[0]; 129 | static immutable withPrefix1Info = str.functionsByOverload[1]; 130 | mixin(withPrefix0Info.importMixin); 131 | 132 | alias withPrefix0 = mixin(withPrefix0Info.aliasMixin); 133 | static assert(is(typeof(&withPrefix0) == typeof(&__traits(getOverloads, modules.types.String, "withPrefix")[0]))); 134 | 135 | alias withPrefix1 = mixin(withPrefix1Info.aliasMixin); 136 | static assert(is(typeof(&withPrefix1) == typeof(&__traits(getOverloads, modules.types.String, "withPrefix")[1]))); 137 | 138 | str.functionsBySymbol.map!(a => a.identifier).should == ["withPrefix"]; 139 | str.functionsBySymbol.length.should == 1; 140 | } 141 | 142 | @("methods.RussianDoll") 143 | @safe pure unittest { 144 | import std.algorithm: find, map; 145 | static immutable mod = module_!"modules.types"; 146 | static immutable info = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.RussianDoll")[0]; 147 | static import modules.types; 148 | alias T = mixin(info.aliasMixin); 149 | static assert(is(T == modules.types.RussianDoll)); 150 | // FIXME 151 | // need to recurse over inner defined types to get to the method. 152 | } 153 | 154 | @("methods.call.variant") 155 | @safe unittest { 156 | import std.algorithm: find; 157 | import std.variant: Variant; 158 | static import modules.types; 159 | 160 | const mod = module_!"modules.types"; 161 | const info = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.String")[0]; 162 | const withPrefix0 = info.functionsByOverload[0]; 163 | const withPrefix1 = info.functionsByOverload[1]; 164 | 165 | auto str = modules.types.String("foo"); 166 | () @trusted { 167 | withPrefix0(&str).get!string.should == "pre_foo"; 168 | withPrefix1(&str, [Variant("quux")]).get!string.should == "quuxfoo"; 169 | }(); 170 | } 171 | 172 | @("methods.call.variant") 173 | @safe unittest { 174 | import std.algorithm: find; 175 | import std.variant: Variant; 176 | static import modules.types; 177 | 178 | const mod = module_!"modules.types"; 179 | const info = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.String")[0]; 180 | const withPrefix0 = info.functionsByOverload[0]; 181 | const withPrefix1 = info.functionsByOverload[1]; 182 | 183 | auto str = modules.types.String("foo"); 184 | () @trusted { 185 | withPrefix0(&str).get!string.should == "pre_foo"; 186 | withPrefix1(&str, [Variant("quux")]).get!string.should == "quuxfoo"; 187 | }(); 188 | } 189 | 190 | @("methods.call.template") 191 | @safe unittest { 192 | import std.algorithm: find; 193 | import std.variant: Variant; 194 | static import modules.types; 195 | 196 | const mod = module_!"modules.types"; 197 | const info = mod.aggregates[].find!(a => a.fullyQualifiedName == "modules.types.String")[0]; 198 | const withPrefix0 = info.functionsByOverload[0]; 199 | const withPrefix1 = info.functionsByOverload[1]; 200 | 201 | auto str = modules.types.String("foo"); 202 | () @trusted { 203 | withPrefix0.methodCall!string(&str).should == "pre_foo"; 204 | withPrefix1.methodCall!string(&str, "quux").should == "quuxfoo"; 205 | }(); 206 | } 207 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/variables.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.variables; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("variables") 8 | @safe pure unittest { 9 | import std.conv: text; 10 | import std.algorithm: map; 11 | 12 | static immutable mod = module_!("modules.variables"); 13 | mod.variables.map!(v => text(v.fullyQualifiedName, `: `, v.type.fullyQualifiedName)).should ~ [ 14 | "modules.variables.gInt: int", 15 | "modules.variables.gDouble: immutable(double)", 16 | "modules.variables.gStruct: modules.variables.Struct", 17 | "modules.variables.CONSTANT_INT: int", 18 | "modules.variables.CONSTANT_STRING: string", 19 | "modules.variables.gImmutableInt: immutable(int)", 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /tests/ut/ctfe/reflection/wrap.d: -------------------------------------------------------------------------------- 1 | module ut.ctfe.reflection.wrap; 2 | 3 | 4 | import ut.ctfe.reflection; 5 | 6 | 7 | @("blub.add1") 8 | unittest { 9 | import blub; 10 | import std.format: format; 11 | 12 | static immutable mod = module_!"modules.functions"; 13 | static immutable add1 = mod.functionsByOverload[0]; 14 | 15 | enum mixinStr = blubWrapperMixin(add1); 16 | //pragma(msg, mixinStr); 17 | mixin(mixinStr); 18 | 19 | wrap(Blub(1), Blub(2)).should == Blub(4); 20 | } 21 | 22 | // Returns a string to be mixed in that defines a function `wrap` 23 | // That calls converts blub types to D ones, calls `function_` then 24 | // converts the result from D to blub. 25 | private string blubWrapperMixin(in Function function_) @safe pure { 26 | assert(__ctfe); 27 | 28 | import std.array: join; 29 | import std.algorithm: map; 30 | import std.range: iota; 31 | import std.format: format; 32 | import std.conv: text; 33 | 34 | string[] lines; 35 | 36 | static string argName(size_t i) { 37 | import std.conv: text; 38 | return text("arg", i); 39 | } 40 | 41 | const numParams = function_.parameters.length; 42 | // what goes in the function signature between the parens 43 | const wrapParams = numParams 44 | .iota 45 | .map!(i => "Blub " ~ argName(i)) 46 | .join(", ") 47 | ; 48 | // the arguments to pass to the wrapped D function 49 | const dArgs = numParams 50 | .iota 51 | .map!(i => argName(i) ~ ".to!(" ~ function_.parameters[i].type.fullyQualifiedName ~ ")") 52 | .join(", ") 53 | ; 54 | 55 | return q{ 56 | auto wrap(%s /*dArgs*/) 57 | { 58 | import blub: toBlub, to; 59 | %s // import mixin 60 | return %s.toBlub; 61 | } 62 | }.format( 63 | wrapParams, 64 | function_.importMixin, 65 | text(function_.fullyQualifiedName, `(`, dArgs, `)`), 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /tests/ut/issues.d: -------------------------------------------------------------------------------- 1 | module ut.issues; 2 | 3 | 4 | import ut; 5 | import mirror.meta.reflection; 6 | import mirror.meta.traits; 7 | import std.meta: AliasSeq; 8 | 9 | 10 | @("1") 11 | @safe pure unittest { 12 | static import modules.issues; 13 | alias mod = Module!"modules.issues"; 14 | // pragma(msg, mod.AllAggregates); 15 | shouldEqual!( 16 | mod.AllAggregates, 17 | AliasSeq!( 18 | modules.issues.Issue1, 19 | modules.issues.CtorProtectionsStruct, 20 | modules.issues.Issue9, 21 | modules.issues.Issue1.String, 22 | ), 23 | ); 24 | } 25 | 26 | 27 | @("MemberFunctionsByOverload.class.templateAlias") 28 | @safe @nogc pure unittest { 29 | 30 | static class Class { 31 | T default_(T)() { return T.init; } // both this and the alias below 32 | alias defaultInt = default_!int; // are needed to mimic a bug 33 | } 34 | 35 | alias fs = MemberFunctionsByOverload!Class; // should compile 36 | } 37 | 38 | 39 | @("MemberFunctionsByOverloads.union") 40 | @safe @nogc pure unittest { 41 | static union Union { 42 | int constant() @safe @nogc pure nothrow const { return 42; } 43 | } 44 | alias fs = MemberFunctionsByOverload!Union; 45 | static assert(fs.length == 1); 46 | } 47 | -------------------------------------------------------------------------------- /tests/ut/meta/reflection/functions.d: -------------------------------------------------------------------------------- 1 | module ut.meta.reflection.functions; 2 | 3 | 4 | import ut.meta.reflection; 5 | import mirror.meta.traits: Parameter; 6 | import std.meta: AliasSeq; 7 | 8 | 9 | @("functions.bySymbol") 10 | @safe pure unittest { 11 | alias mod = Module!("modules.functions"); 12 | import modules.functions; 13 | 14 | alias expected = AliasSeq!( 15 | FunctionSymbol!(addd, Protection.public_, Linkage.D), 16 | FunctionSymbol!(withDefault, Protection.public_, Linkage.D), 17 | FunctionSymbol!(storageClasses, Protection.public_, Linkage.D), 18 | FunctionSymbol!(exportedFunc, Protection.export_, Linkage.D), 19 | FunctionSymbol!(externC, Protection.public_, Linkage.C), 20 | FunctionSymbol!(externCpp, Protection.public_, Linkage.Cpp), 21 | FunctionSymbol!(identityInt, Protection.public_, Linkage.D, "identityInt", modules.functions), 22 | FunctionSymbol!(voldemort, Protection.public_, Linkage.D), 23 | FunctionSymbol!(voldemortArray, Protection.public_, Linkage.D), 24 | FunctionSymbol!(concatFoo, Protection.public_, Linkage.D), 25 | ); 26 | 27 | // pragma(msg, "\n", mod.FunctionsBySymbol.stringof, "\n"); 28 | shouldEqual!(mod.FunctionsBySymbol, expected); 29 | 30 | static assert(mod.FunctionsBySymbol[0].overloads.length == 2); // addd 31 | static foreach(i; 1..expected.length) 32 | static assert(mod.FunctionsBySymbol[i].overloads.length == 1); // everything else 33 | } 34 | 35 | 36 | @("functions.byOverload") 37 | @safe pure unittest { 38 | alias mod = Module!("modules.functions"); 39 | import modules.functions; 40 | 41 | alias adddInt = __traits(getOverloads, modules.functions, "addd")[0]; 42 | alias adddDouble = __traits(getOverloads, modules.functions, "addd")[1]; 43 | 44 | alias expected = AliasSeq!( 45 | FunctionOverload!(adddInt, Protection.public_, Linkage.D), 46 | FunctionOverload!(adddDouble, Protection.public_, Linkage.D, "addd", modules.functions, 1), 47 | FunctionOverload!(withDefault, Protection.public_, Linkage.D), 48 | FunctionOverload!(storageClasses, Protection.public_, Linkage.D), 49 | FunctionOverload!(exportedFunc, Protection.export_, Linkage.D), 50 | FunctionOverload!(externC, Protection.public_, Linkage.C), 51 | FunctionOverload!(externCpp, Protection.public_, Linkage.Cpp), 52 | FunctionOverload!(identityInt, Protection.public_, Linkage.D, "identityInt", modules.functions), 53 | FunctionOverload!(voldemort, Protection.public_, Linkage.D), 54 | FunctionOverload!(voldemortArray, Protection.public_, Linkage.D), 55 | FunctionOverload!(concatFoo, Protection.public_, Linkage.D), 56 | ); 57 | 58 | // pragma(msg, "\n", mod.FunctionsByOverload.stringof, "\n"); 59 | shouldEqual!(mod.FunctionsByOverload, expected); 60 | } 61 | 62 | 63 | @("problems") 64 | @safe pure unittest { 65 | alias mod = Module!("modules.problems"); 66 | static assert(mod.FunctionsBySymbol.length == 0, mod.FunctionsBySymbol.stringof); 67 | } 68 | 69 | 70 | 71 | @("parameters.addd.bySymbol") 72 | @safe pure unittest { 73 | 74 | alias mod = Module!("modules.functions"); 75 | alias adddInt = mod.FunctionsBySymbol[0].overloads[0]; 76 | alias adddDouble = mod.FunctionsBySymbol[0].overloads[1]; 77 | alias withDefaults = mod.FunctionsBySymbol[1].overloads[0]; 78 | 79 | shouldEqual!( 80 | adddInt.parameters, 81 | AliasSeq!( 82 | Parameter!(int, void, "i"), 83 | Parameter!(int, void, "j"), 84 | ) 85 | ); 86 | 87 | shouldEqual!( 88 | adddDouble.parameters, 89 | AliasSeq!( 90 | Parameter!(double, void, "d0"), 91 | Parameter!(double, void, "d1"), 92 | ) 93 | ); 94 | 95 | shouldEqual!( 96 | withDefaults.parameters, 97 | AliasSeq!( 98 | Parameter!(double, void, "fst"), 99 | Parameter!(double, 33.3, "snd"), 100 | ) 101 | ); 102 | } 103 | 104 | 105 | @("parameters.addd.byOverload") 106 | @safe pure unittest { 107 | alias mod = Module!("modules.functions"); 108 | alias adddInt = mod.FunctionsByOverload[0]; 109 | alias adddDouble = mod.FunctionsByOverload[1]; 110 | 111 | shouldEqual!( 112 | adddInt.parameters, 113 | AliasSeq!( 114 | Parameter!(int, void, "i"), 115 | Parameter!(int, void, "j"), 116 | ) 117 | ); 118 | 119 | shouldEqual!( 120 | adddDouble.parameters, 121 | AliasSeq!( 122 | Parameter!(double, void, "d0"), 123 | Parameter!(double, void, "d1"), 124 | ) 125 | ); 126 | } 127 | 128 | 129 | @("parameters.storageClasses") 130 | @safe pure unittest { 131 | 132 | import std.traits: STC = ParameterStorageClass; 133 | 134 | alias mod = Module!("modules.functions"); 135 | alias storageClasses = mod.FunctionsByOverload[3]; 136 | 137 | // pragma(msg, "\n", storageClasses.parameters.stringof, "\n"); 138 | 139 | shouldEqual!( 140 | storageClasses.parameters, 141 | AliasSeq!( 142 | Parameter!(int, void, "normal", STC.none), 143 | Parameter!(int*, void, "returnScope", STC.return_ | STC.scope_), 144 | Parameter!(int, void, "out_", STC.out_), 145 | Parameter!(int, void, "ref_", STC.ref_), 146 | Parameter!(int, void, "lazy_", STC.lazy_), 147 | ) 148 | ); 149 | } 150 | 151 | 152 | 153 | @("return.bySymbol") 154 | @safe pure unittest { 155 | import std.meta: staticMap; 156 | 157 | alias mod = Module!("modules.functions"); 158 | alias functions = mod.FunctionsBySymbol; 159 | 160 | static assert(is(functions[0].overloads[0].ReturnType == int)); 161 | static assert(is(functions[0].overloads[1].ReturnType == double)); 162 | static assert(is(functions[1].overloads[0].ReturnType == double)); 163 | static assert(is(functions[2].overloads[0].ReturnType == void)); 164 | } 165 | 166 | 167 | @("return.byOverload") 168 | @safe pure unittest { 169 | import std.meta: staticMap; 170 | import std.traits: ReturnType; 171 | static import modules.functions; 172 | 173 | alias mod = Module!("modules.functions"); 174 | alias return_(alias F) = F.ReturnType; 175 | alias returnTypes = staticMap!(return_, mod.FunctionsByOverload); 176 | 177 | shouldEqual!( 178 | returnTypes, 179 | AliasSeq!( 180 | int, double, double, void, void, void, void, int, 181 | ReturnType!(modules.functions.voldemort), 182 | ReturnType!(modules.functions.voldemortArray), 183 | string, 184 | ), 185 | ); 186 | } 187 | -------------------------------------------------------------------------------- /tests/ut/meta/reflection/package.d: -------------------------------------------------------------------------------- 1 | module ut.meta.reflection; 2 | 3 | public import ut; 4 | public import mirror.meta.reflection; 5 | public import mirror.trait_enums; 6 | -------------------------------------------------------------------------------- /tests/ut/meta/reflection/types.d: -------------------------------------------------------------------------------- 1 | module ut.meta.reflection.types; 2 | 3 | 4 | import ut.meta.reflection; 5 | 6 | 7 | @("empty") 8 | @safe pure unittest { 9 | alias mod = Module!"modules.empty"; 10 | typeNames!mod.shouldBeEmpty; 11 | } 12 | 13 | 14 | @("imports") 15 | @safe pure unittest { 16 | alias mod = Module!"modules.imports"; 17 | typeNames!mod.shouldBeEmpty; 18 | } 19 | 20 | 21 | @("types") 22 | @safe pure unittest { 23 | alias mod = Module!"modules.types"; 24 | typeNames!mod.shouldBeSameSetAs( 25 | [ 26 | "String", 27 | "Enum", 28 | "Class", 29 | "Interface", 30 | "AbstractClass", 31 | "MiddleClass", 32 | "LeafClass", 33 | "Point", 34 | "Inner1", 35 | "EvenInner", 36 | "Inner2", 37 | "Outer", 38 | "Char", 39 | "Union", 40 | "RussianDoll", 41 | ] 42 | ); 43 | } 44 | 45 | 46 | @("problems") 47 | @safe pure unittest { 48 | alias mod = Module!"modules.problems"; 49 | typeNames!mod.should == ["PrivateFields"]; 50 | } 51 | 52 | 53 | @("variables") 54 | @safe pure unittest { 55 | alias mod = Module!"modules.variables"; 56 | typeNames!mod.should == ["Struct"]; 57 | } 58 | 59 | 60 | private string[] typeNames(alias module_)() { 61 | import std.meta: staticMap; 62 | enum name(alias Symbol) = __traits(identifier, Symbol); 63 | enum names = staticMap!(name, module_.Aggregates); 64 | return [names]; 65 | } 66 | -------------------------------------------------------------------------------- /tests/ut/meta/reflection/variables.d: -------------------------------------------------------------------------------- 1 | module ut.meta.reflection.variables; 2 | 3 | 4 | import ut.meta.reflection; 5 | import std.meta: AliasSeq; 6 | 7 | 8 | @("variables") 9 | @safe pure unittest { 10 | static import modules.variables; 11 | 12 | alias mod = Module!("modules.variables"); 13 | static assert(__traits(isSame, mod.Variables[0], Variable!(int, "gInt", 0, false))); 14 | static assert(__traits(isSame, mod.Variables[3], Variable!(int, "CONSTANT_INT", 42, true))); 15 | } 16 | 17 | 18 | @("problems") 19 | @safe pure unittest { 20 | static import modules.variables; 21 | 22 | alias mod = Module!("modules.problems"); 23 | 24 | static assert(mod.Variables.length == 1, mod.Variables.stringof); 25 | 26 | shouldEqual!( 27 | mod.Variables, 28 | AliasSeq!( 29 | Variable!(int[], "gInts", (int[]).init, false), 30 | ) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /tests/ut/meta/traits.d: -------------------------------------------------------------------------------- 1 | module ut.meta.traits; 2 | 3 | 4 | import ut; 5 | import mirror.meta.reflection; 6 | import mirror.meta.traits; 7 | import std.meta: AliasSeq; 8 | 9 | 10 | @("isEnum") 11 | @safe pure unittest { 12 | static import modules.types; 13 | import std.meta: Filter, AliasSeq; 14 | 15 | alias mod = Module!"modules.types"; 16 | alias aggregates = mod.Aggregates; 17 | alias enums = Filter!(isEnum, aggregates); 18 | shouldEqual!(enums, AliasSeq!(modules.types.Enum, modules.types.Char)); 19 | } 20 | 21 | 22 | @("isStruct") 23 | @safe pure unittest { 24 | static import modules.types; 25 | import std.meta: Filter, AliasSeq; 26 | 27 | alias mod = Module!"modules.types"; 28 | alias aggregates = mod.Aggregates; 29 | alias structs = Filter!(isStruct, aggregates); 30 | static assert(is(structs == 31 | AliasSeq!( 32 | modules.types.String, 33 | modules.types.Point, 34 | modules.types.Inner1, 35 | modules.types.EvenInner, 36 | modules.types.Inner2, 37 | modules.types.Outer, 38 | modules.types.RussianDoll, 39 | ) 40 | ), 41 | structs.stringof); 42 | } 43 | 44 | 45 | @("isInterface") 46 | @safe pure unittest { 47 | static import modules.types; 48 | import std.meta: Filter, AliasSeq; 49 | 50 | alias mod = Module!"modules.types"; 51 | alias aggregates = mod.Aggregates; 52 | alias interfaces = Filter!(isInterface, aggregates); 53 | static assert(is(interfaces == AliasSeq!(modules.types.Interface)), interfaces.stringof); 54 | } 55 | 56 | 57 | @("isClass") 58 | @safe pure unittest { 59 | static import modules.types; 60 | import std.meta: Filter, AliasSeq; 61 | 62 | alias mod = Module!"modules.types"; 63 | alias aggregates = mod.Aggregates; 64 | alias classes = Filter!(isClass, aggregates); 65 | alias expected = AliasSeq!( 66 | modules.types.Class, 67 | modules.types.AbstractClass, 68 | modules.types.MiddleClass, 69 | modules.types.LeafClass, 70 | ); 71 | static assert(is(classes == expected), classes.stringof); 72 | } 73 | 74 | 75 | @("isOOP") 76 | @safe pure unittest { 77 | static import modules.types; 78 | import std.meta: Filter, AliasSeq; 79 | 80 | alias mod = Module!"modules.types"; 81 | alias aggregates = mod.Aggregates; 82 | alias classes = Filter!(isOOP, aggregates); 83 | alias expected = AliasSeq!( 84 | modules.types.Class, 85 | modules.types.Interface, 86 | modules.types.AbstractClass, 87 | modules.types.MiddleClass, 88 | modules.types.LeafClass, 89 | ); 90 | static assert(is(classes == expected), classes.stringof); 91 | } 92 | 93 | 94 | @("FundamentalType.scalar") 95 | @safe pure unittest { 96 | static assert(is(FundamentalType!int == int)); 97 | static assert(is(FundamentalType!double == double)); 98 | static struct Foo { } 99 | static assert(is(FundamentalType!Foo == Foo)); 100 | } 101 | 102 | 103 | @("FundamentalType.array") 104 | @safe pure unittest { 105 | static assert(is(FundamentalType!(int[]) == int)); 106 | static assert(is(FundamentalType!(int[][]) == int)); 107 | static assert(is(FundamentalType!(int[][][]) == int)); 108 | 109 | static assert(is(FundamentalType!(double[]) == double)); 110 | static assert(is(FundamentalType!(double[][]) == double)); 111 | 112 | static assert(is(FundamentalType!string == immutable char)); 113 | static assert(is(FundamentalType!(string[]) == immutable char)); 114 | 115 | static struct Foo { } 116 | static assert(is(FundamentalType!(Foo[]) == Foo)); 117 | static assert(is(FundamentalType!(Foo[][]) == Foo)); 118 | } 119 | 120 | 121 | @("FundamentalType.pointer") 122 | @safe pure unittest { 123 | static assert(is(FundamentalType!(int*) == int)); 124 | static assert(is(FundamentalType!(int**) == int)); 125 | static assert(is(FundamentalType!(int***) == int)); 126 | 127 | static assert(is(FundamentalType!(double*) == double)); 128 | static assert(is(FundamentalType!(double**) == double)); 129 | 130 | static assert(is(FundamentalType!(string*) == immutable char)); 131 | static assert(is(FundamentalType!(string**) == immutable char)); 132 | 133 | static struct Foo { } 134 | static assert(is(FundamentalType!(Foo*) == Foo)); 135 | static assert(is(FundamentalType!(Foo**) == Foo)); 136 | } 137 | 138 | 139 | @("RecursiveFieldTypes.scalar") 140 | @safe pure unittest { 141 | static assert(is(RecursiveFieldTypes!int == int)); 142 | static assert(is(RecursiveFieldTypes!double == double)); 143 | } 144 | 145 | 146 | @("RecursiveFieldTypes.udt.flat") 147 | @safe pure unittest { 148 | 149 | static struct Foo { 150 | int i; 151 | double d; 152 | } 153 | 154 | shouldEqual!(RecursiveFieldTypes!Foo, AliasSeq!(int, double)); 155 | } 156 | 157 | 158 | @("RecursiveFieldTypes.udt.nested") 159 | @safe pure unittest { 160 | 161 | static struct Inner0 { 162 | int i; 163 | double d; 164 | } 165 | 166 | static struct Inner1 { 167 | double d; 168 | string s; 169 | } 170 | 171 | static struct Mid { 172 | Inner0 inner0; 173 | Inner1 inner1; 174 | } 175 | 176 | static struct Outer { 177 | Mid mid; 178 | byte b; 179 | float func(float, float); 180 | 181 | @property static Outer max() @safe pure nothrow @nogc { 182 | return Outer(); 183 | } 184 | } 185 | 186 | shouldEqual!(RecursiveFieldTypes!Outer, 187 | AliasSeq!(Mid, Inner0, int, double, Inner1, string, byte)); 188 | } 189 | 190 | 191 | @("RecursiveFieldTypes.udt.Date") 192 | @safe pure unittest { 193 | 194 | import std.datetime: Date, Month; 195 | 196 | static struct Struct { 197 | int i; 198 | Date date; 199 | } 200 | 201 | shouldEqual!(RecursiveFieldTypes!Struct, 202 | AliasSeq!(int, Date, short, Month, ubyte)); 203 | } 204 | 205 | 206 | @("RecursiveFieldTypes.udt.DateTime") 207 | @safe pure unittest { 208 | 209 | import std.datetime: Date, DateTime, Month, TimeOfDay; 210 | 211 | static struct Struct { 212 | int i; 213 | DateTime date; 214 | } 215 | 216 | shouldEqual!(RecursiveFieldTypes!Struct, 217 | AliasSeq!(int, DateTime, Date, short, Month, ubyte, TimeOfDay)); 218 | } 219 | 220 | 221 | @("RecursiveFieldTypes.udt.composite.struct") 222 | @safe pure unittest { 223 | 224 | static struct Struct { 225 | Struct* child; 226 | } 227 | 228 | shouldEqual!(RecursiveFieldTypes!Struct, Struct*); 229 | } 230 | 231 | 232 | @("RecursiveFieldTypes.udt.composite.class.simple") 233 | @safe pure unittest { 234 | 235 | static class Class { 236 | Class child; 237 | } 238 | 239 | shouldEqual!(RecursiveFieldTypes!Class, Class); 240 | } 241 | 242 | 243 | @("RecursiveFieldTypes.udt.composite.class.multiple") 244 | @safe pure unittest { 245 | 246 | shouldEqual!(RecursiveFieldTypes!RecursiveClass0, 247 | AliasSeq!(RecursiveClass1, RecursiveClass2)); 248 | } 249 | 250 | 251 | private class RecursiveClass0 { 252 | RecursiveClass1 child; 253 | } 254 | 255 | private class RecursiveClass1 { 256 | RecursiveClass2 child; 257 | } 258 | 259 | private class RecursiveClass2 { 260 | RecursiveClass0 child; 261 | } 262 | 263 | 264 | @("RecursiveFieldTypes.udt.composite.array") 265 | @safe pure unittest { 266 | 267 | static struct Point(T) { 268 | T x, y; 269 | } 270 | 271 | struct Inner1(T) { 272 | Point!T point; 273 | T value; 274 | } 275 | 276 | struct EvenInner(T) { 277 | T value; 278 | } 279 | 280 | struct Inner2(T) { 281 | EvenInner!T evenInner; 282 | } 283 | 284 | static struct Outer(T) { 285 | Inner1!T[] inner1s; 286 | Inner2!T inner2; 287 | } 288 | 289 | // pragma(msg, RecursiveFieldTypes!(Outer!double)); 290 | shouldEqual!(RecursiveFieldTypes!(Outer!double), 291 | AliasSeq!(Inner1!double[], Point!double, double, Inner2!double, EvenInner!double)); 292 | } 293 | 294 | 295 | @("RecursiveFieldTypes.SocketOSException") 296 | @safe @nogc pure unittest { 297 | import std.socket: SocketOSException; 298 | alias types = RecursiveFieldTypes!SocketOSException; 299 | //pragma(msg, types); 300 | shouldEqual!(types, AliasSeq!int); 301 | } 302 | 303 | 304 | @("isProperty.struct") 305 | @safe @nogc pure unittest { 306 | 307 | static struct Struct { 308 | int _i; 309 | @property int i(); 310 | @property void i(int i); 311 | int foo(double d); 312 | } 313 | 314 | static assert(isProperty!(__traits(getOverloads, Struct, "i")[1])); 315 | static assert(isProperty!(__traits(getOverloads, Struct, "i")[1])); 316 | static assert(!isProperty!(Struct.foo)); 317 | } 318 | 319 | 320 | @("isProperty.class") 321 | @safe @nogc pure unittest { 322 | 323 | static class Class { 324 | int _i; 325 | @property int i() { return _i; } 326 | @property void i(int i) { _i = i; } 327 | int foo(double d) { return i * 2; } 328 | } 329 | 330 | static assert(isProperty!(__traits(getOverloads, Class, "i")[1])); 331 | static assert(isProperty!(__traits(getOverloads, Class, "i")[1])); 332 | static assert(!isProperty!(Class.foo)); 333 | } 334 | 335 | 336 | @("MemberFunctionsByOverload.struct") 337 | @safe @nogc pure unittest { 338 | 339 | static struct Struct { 340 | private int _i; 341 | @property int i(); 342 | @property void i(int i); 343 | int foo(double d); 344 | string bar(int i); 345 | } 346 | 347 | //pragma(msg, "MemberFunctionsByOverload.struct: ", MemberFunctionsByOverload!Struct.stringof); 348 | 349 | shouldEqual!( 350 | MemberFunctionsByOverload!Struct, 351 | AliasSeq!( 352 | __traits(getOverloads, Struct, "i")[0], 353 | __traits(getOverloads, Struct, "i")[1], 354 | Struct.foo, 355 | Struct.bar, 356 | ) 357 | ); 358 | } 359 | 360 | 361 | @("MemberFunctionsByOverload.class.simple") 362 | @safe @nogc pure unittest { 363 | 364 | static class Class { 365 | private int _i; 366 | @property int i() { return _i; } 367 | @property void i(int i) { _i = i; } 368 | int foo(double d) { return cast(int) (d * 2); } 369 | string bar(int i) { return "foobar"; } 370 | } 371 | 372 | // pragma(msg, "MemberFunctionsByOverload.class: ", MemberFunctionsByOverload!Class.stringof); 373 | 374 | shouldEqual!( 375 | MemberFunctionsByOverload!Class, 376 | AliasSeq!( 377 | __traits(getOverloads, Class, "i")[0], 378 | __traits(getOverloads, Class, "i")[1], 379 | Class.foo, 380 | Class.bar, 381 | ) 382 | ); 383 | } 384 | 385 | 386 | @("MemberFunctionsByOverload.std.stdio.File") 387 | @safe @nogc pure unittest { 388 | import std.stdio: File; 389 | alias functions = MemberFunctionsByOverload!File; 390 | static assert(functions.length > 0); 391 | } 392 | 393 | @("MemberFunctionsByOverload.CtorProtectionsStruct") 394 | @safe @nogc pure unittest { 395 | import modules.issues: CtorProtectionsStruct; 396 | alias functions = MemberFunctionsByOverload!CtorProtectionsStruct; 397 | //pragma(msg, functions.stringof); 398 | static assert(functions.length == 1); // the only public constructor 399 | } 400 | 401 | 402 | @("PublicMembers.std.socket") 403 | @safe @nogc pure unittest { 404 | import std.socket; 405 | alias members = PublicMembers!(std.socket); 406 | } 407 | 408 | 409 | 410 | @("isStaticMemberFunction") 411 | @safe @nogc pure unittest { 412 | static struct Struct { 413 | int foo(); 414 | static int bar(); 415 | } 416 | 417 | static void fun() {} 418 | 419 | static assert(!isStaticMemberFunction!(Struct.foo)); 420 | static assert( isStaticMemberFunction!(Struct.bar)); 421 | static assert(!isStaticMemberFunction!fun); 422 | static assert(!isStaticMemberFunction!staticGlobalFunc); 423 | } 424 | 425 | 426 | static void staticGlobalFunc() { 427 | 428 | } 429 | 430 | 431 | @("BinaryOperators") 432 | @safe @nogc pure unittest { 433 | 434 | static struct Number { 435 | int i; 436 | Number opBinary(string op)(Number other) if(op == "+") { 437 | return Number(i + other.i); 438 | } 439 | Number opBinary(string op)(Number other) if(op == "-") { 440 | return Number(i - other.i); 441 | } 442 | Number opBinaryRight(string op)(int other) if(op == "+") { 443 | return Number(i + other); 444 | } 445 | } 446 | 447 | static assert( 448 | [BinaryOperators!Number] == 449 | [ 450 | BinaryOperator("+", BinOpDir.left | BinOpDir.right), 451 | BinaryOperator("-", BinOpDir.left), 452 | ] 453 | ); 454 | } 455 | 456 | 457 | @("UnaryOperators") 458 | @safe pure unittest { 459 | 460 | static struct Struct { 461 | int opUnary(string op)() if(op == "+") { return 42; } 462 | int opUnary(string op)() if(op == "~") { return 33; } 463 | } 464 | 465 | static assert([UnaryOperators!Struct] == ["+", "~"]); 466 | } 467 | 468 | 469 | @("AssignOperators") 470 | @safe pure unittest { 471 | 472 | static struct Number { 473 | int i; 474 | Number opOpAssign(string op)(Number other) if(op == "+") { 475 | return Number(i + other.i); 476 | } 477 | Number opOpAssign(string op)(Number other) if(op == "-") { 478 | return Number(i - other.i); 479 | } 480 | Number opOpAssignRight(string op)(int other) if(op == "+") { 481 | return Number(i + other); 482 | } 483 | } 484 | 485 | static assert([AssignOperators!Number] == ["+", "-"]); 486 | } 487 | 488 | 489 | @("NumDefaultParameters") 490 | @safe pure unittest { 491 | 492 | static void none0(); 493 | static assert(NumDefaultParameters!none0 == 0); 494 | 495 | static void none1(int i); 496 | static assert(NumDefaultParameters!none1 == 0); 497 | 498 | static void one(int i, double d = 33.3); 499 | static assert(NumDefaultParameters!one == 1); 500 | 501 | static void two(int i, double d = 33.3, int j = 42); 502 | static assert(NumDefaultParameters!two == 2); 503 | } 504 | 505 | 506 | @("NumRequiredParameters") 507 | @safe pure unittest { 508 | 509 | static void none(); 510 | static assert(NumRequiredParameters!none == 0); 511 | 512 | static void one0(int i, double d = 33.3); 513 | static assert(NumRequiredParameters!one0 == 1); 514 | 515 | static void one1(int i, double d = 33.3, int j = 42); 516 | static assert(NumRequiredParameters!one1 == 1); 517 | 518 | static void two(int i, string s, double d = 33.3, int j = 42); 519 | static assert(NumRequiredParameters!two == 2); 520 | } 521 | 522 | 523 | @("Parameters.default.function.ptr") 524 | @safe pure unittest { 525 | static string defaultFormatter(int) { return "oops"; } 526 | static void func(string function(int) @trusted errorFormatter = &defaultFormatter); 527 | alias params = Parameters!func; 528 | } 529 | 530 | 531 | @("isMutableSymbol") 532 | @safe pure unittest { 533 | static import modules.variables; 534 | static assert( isMutableSymbol!(modules.variables.gInt)); 535 | static assert(!isMutableSymbol!(modules.variables.gDouble)); 536 | static assert( isMutableSymbol!(modules.variables.gStruct)); 537 | static assert(!isMutableSymbol!(modules.variables.CONSTANT_INT)); 538 | static assert(!isMutableSymbol!(modules.variables.CONSTANT_STRING)); 539 | static assert(!isMutableSymbol!(modules.variables.gImmutableInt)); 540 | } 541 | 542 | 543 | @("isVariable") 544 | @safe pure unittest { 545 | static import modules.variables; 546 | 547 | alias member(string name) = MemberFromName!(modules.variables, name); 548 | 549 | static assert( isVariable!(member!"gInt")); 550 | static assert(!isVariable!(member!"Struct")); 551 | static assert(!isVariable!(member!"templateFunction")); 552 | } 553 | 554 | 555 | @("Fields.struct.0") 556 | @safe pure unittest { 557 | 558 | static struct Struct { 559 | int i; 560 | string s; 561 | } 562 | 563 | shouldEqual!( 564 | Fields!Struct, 565 | Field!(int, "i"), Field!(string, "s"), 566 | ); 567 | } 568 | 569 | 570 | @("Fields.struct.1") 571 | @safe pure unittest { 572 | 573 | import mirror.trait_enums: Protection; 574 | 575 | static struct Struct { 576 | double d; 577 | byte b; 578 | private int i; 579 | string s; 580 | } 581 | 582 | shouldEqual!( 583 | Fields!Struct, 584 | 585 | Field!(double, "d"), 586 | Field!(byte, "b"), 587 | Field!(int, "i", Protection.private_), 588 | Field!(string, "s"), 589 | ); 590 | } 591 | 592 | 593 | @("Fields.class") 594 | @safe pure unittest { 595 | 596 | static abstract class Abstract {} 597 | 598 | static class Base: Abstract { 599 | int i; 600 | } 601 | 602 | static class Child: Base { 603 | string s; 604 | this(string s) { this.s = s ~ "_suffix"; } 605 | } 606 | 607 | // pragma(msg, Fields!Child); 608 | 609 | shouldEqual!( 610 | Fields!Child, 611 | Field!(string, "s"), Field!(int, "i"), 612 | ); 613 | } 614 | -------------------------------------------------------------------------------- /tests/ut/package.d: -------------------------------------------------------------------------------- 1 | module ut; 2 | 3 | 4 | public import unit_threaded; 5 | 6 | 7 | // Passing two AliasSeqs causes them to collapse into their concatenated contents 8 | void shouldEqual(A...)() { 9 | 10 | import std.conv: text; 11 | 12 | static assert(A.length % 2 == 0, 13 | text("\n\n", 14 | "Can only compare even number of template arguments, not sequence of length ", 15 | A.length, ":\n\n", 16 | A.stringof, 17 | "\n\n", 18 | ) 19 | ); 20 | 21 | static foreach(i; 0 .. A.length / 2) {{ 22 | enum j = i + A.length / 2; 23 | static assert(__traits(isSame, A[i], A[j]), 24 | text("\n\n", 25 | " Expected: ", A[A.length / 2 .. $].stringof, 26 | "\n\n", 27 | " Got: ", A[0 .. A.length / 2].stringof, 28 | "\n\n", 29 | ) 30 | ); 31 | }} 32 | } 33 | -------------------------------------------------------------------------------- /tests/ut/rtti/any.d: -------------------------------------------------------------------------------- 1 | module ut.rtti.any; 2 | 3 | 4 | import ut; 5 | import mirror.rtti; 6 | 7 | 8 | @("types.fundamental") 9 | @safe unittest { 10 | with(types!(int, double)) { 11 | rtti(42).name.should == "int"; 12 | rtti(33.3).name.should == "double"; 13 | } 14 | } 15 | 16 | @("rtti.null.object") 17 | @safe unittest { 18 | static class Class {} 19 | static interface Interface{} 20 | Object o; 21 | Class c; 22 | Interface i; 23 | 24 | with(types!Class) { 25 | rtti(o).shouldThrowWithMessage("Cannot get RTTI from null object"); 26 | rtti(c).shouldThrowWithMessage("Cannot get RTTI from null object"); 27 | rtti(i).shouldThrowWithMessage("Cannot get RTTI from null object"); 28 | } 29 | } 30 | 31 | 32 | @("rtti.unregistered") 33 | @safe unittest { 34 | static class Class {} 35 | enum testName = __traits(identifier, __traits(parent, {})); 36 | enum prefix = __MODULE__ ~ "." ~ testName ~ "."; 37 | 38 | with(types!()) { 39 | rtti(new Class).shouldThrowWithMessage( 40 | "Cannot get RTTI for unregistered type " ~ 41 | prefix ~ "Class" 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ut/rtti/oop.d: -------------------------------------------------------------------------------- 1 | module ut.rtti.oop; 2 | 3 | 4 | import ut; 5 | import mirror.rtti; 6 | 7 | 8 | @("name") 9 | @safe unittest { 10 | 11 | static abstract class Abstract { 12 | string getName() @safe pure nothrow scope const; 13 | } 14 | 15 | static class Foo: Abstract { 16 | override string getName() @safe pure nothrow scope const { 17 | return "Foo"; 18 | } 19 | } 20 | 21 | static class Bar: Abstract { 22 | override string getName() @safe pure nothrow scope const { 23 | return "Bar"; 24 | } 25 | } 26 | 27 | // explicit Abstract and not auto so as to erase the type 28 | // of the value 29 | const Abstract foo = new Foo(); 30 | const Abstract bar = new Bar(); 31 | 32 | with(types!(Foo, Bar)) { 33 | const fooType = rtti(foo); 34 | const barType = rtti(bar); 35 | 36 | enum testId = __traits(identifier, __traits(parent, {})); 37 | enum prefix = __MODULE__ ~ "." ~ testId ~ "."; 38 | 39 | fooType.name.should == prefix ~ "Foo"; 40 | barType.name.should == prefix ~ "Bar"; 41 | } 42 | } 43 | 44 | 45 | @("typeInfo") 46 | // The test is neither @safe nor pure because Object.opEquals isn't 47 | @system unittest { 48 | 49 | static abstract class Abstract { } 50 | static class Class: Abstract { } 51 | const Abstract obj = new Class(); 52 | 53 | with(types!Class) { 54 | auto type = rtti(obj); 55 | assert(type.typeInfo != typeid(int)); 56 | assert(type.typeInfo != typeid(Abstract)); 57 | assert(type.typeInfo == typeid(Class)); 58 | } 59 | } 60 | 61 | 62 | @("fields.typeInfo.0") 63 | @system unittest { 64 | import std.algorithm: map; 65 | import std.array: array; 66 | 67 | static abstract class Abstract {} 68 | static class Class: Abstract { 69 | int i; 70 | string s; 71 | } 72 | const Abstract obj = new Class; 73 | 74 | with(types!Class) { 75 | const type = rtti(obj); 76 | type.fields.map!(a => a.type.typeInfo).array.shouldEqual( 77 | [ 78 | typeid(int), 79 | typeid(string), 80 | ] 81 | ); 82 | } 83 | } 84 | 85 | 86 | // needed because of TypeInfo.opEquals being non-const 87 | private void shouldEqual( 88 | const(TypeInfo)[] lhs, 89 | const(TypeInfo)[] rhs, 90 | in string file = __FILE__, 91 | in size_t line = __LINE__) 92 | @trusted // TypeInfo.opEquals 93 | { 94 | import unit_threaded : _shouldEqual = shouldEqual; 95 | (cast(TypeInfo[]) lhs)._shouldEqual(cast (TypeInfo[]) rhs, file, line); 96 | } 97 | 98 | 99 | @("fields.type.0") 100 | @safe unittest { 101 | import std.algorithm: map; 102 | 103 | static abstract class Abstract {} 104 | static class Class: Abstract { 105 | int i; 106 | string s; 107 | } 108 | const Abstract obj = new Class; 109 | 110 | with(types!Class) { 111 | const type = rtti(obj); 112 | type.fields.map!(a => a.type.name).should == [ "int", "immutable(char)[]" ]; 113 | } 114 | } 115 | 116 | 117 | @("fields.id.0") 118 | @safe unittest { 119 | import std.algorithm: map; 120 | 121 | static abstract class Abstract {} 122 | static class Class: Abstract { 123 | int i; 124 | string s; 125 | } 126 | const Abstract obj = new Class; 127 | 128 | with(types!Class) { 129 | const type = rtti(obj); 130 | type.fields.map!(a => a.identifier).should == [ "i", "s" ]; 131 | } 132 | } 133 | 134 | 135 | @("fields.get.0") 136 | @system /* typeInfo */ unittest { 137 | 138 | static abstract class Abstract {} 139 | static class Class: Abstract { 140 | int i; 141 | string s; 142 | this(int i, string s) { this.i = i; this.s = s; } 143 | } 144 | const Abstract obj = new Class(42, "foobar"); 145 | 146 | with(types!Class) { 147 | const type = rtti(obj); 148 | 149 | type.fields[0].get!int(obj).should == 42; 150 | type.fields[0].get!string(obj).shouldThrow; 151 | 152 | type.fields[1].get!int(obj).shouldThrow; 153 | type.fields[1].get!string(obj).should == "foobar"; 154 | } 155 | } 156 | 157 | 158 | @("fields.typeInfo.1") 159 | @system unittest { 160 | 161 | import std.algorithm: map; 162 | import std.array: array; 163 | 164 | static abstract class Abstract {} 165 | static class Class: Abstract { 166 | string s0; 167 | string s1; 168 | double d; 169 | string s2; 170 | } 171 | const Abstract obj = new Class(); 172 | 173 | with(types!Class) { 174 | const type = rtti(obj); 175 | type.fields.map!(a => a.type.typeInfo).array.shouldEqual( 176 | [ 177 | typeid(string), 178 | typeid(string), 179 | typeid(double), 180 | typeid(string), 181 | ] 182 | ); 183 | } 184 | } 185 | 186 | 187 | @("fields.type.1") 188 | @safe unittest { 189 | 190 | import std.algorithm: map; 191 | 192 | static abstract class Abstract {} 193 | static class Class: Abstract { 194 | string s0; 195 | string s1; 196 | double d; 197 | string s2; 198 | } 199 | const Abstract obj = new Class(); 200 | 201 | with(types!Class) { 202 | const type = rtti(obj); 203 | type.fields.map!(a => a.type.name).should == [ 204 | "immutable(char)[]", 205 | "immutable(char)[]", 206 | "double", 207 | "immutable(char)[]", 208 | ]; 209 | } 210 | } 211 | 212 | 213 | @("fields.id.1") 214 | @safe unittest { 215 | 216 | import std.algorithm: map; 217 | 218 | static abstract class Abstract {} 219 | static class Class: Abstract { 220 | string s0; 221 | string s1; 222 | double d; 223 | string s2; 224 | } 225 | const Abstract obj = new Class(); 226 | 227 | with(types!Class) { 228 | const type = rtti(obj); 229 | type.fields.map!(a => a.identifier).should == [ 230 | "s0", 231 | "s1", 232 | "d", 233 | "s2", 234 | ]; 235 | } 236 | } 237 | 238 | @("fields.get.1") 239 | @safe unittest { 240 | 241 | import std.algorithm: map; 242 | 243 | static abstract class Abstract {} 244 | static class Class: Abstract { 245 | string s0; 246 | private string s1; 247 | double d; 248 | string s2; 249 | this(string s0, double d, string s2) { 250 | this.s0 = s0; 251 | this.s1 = "nope"; 252 | this.d = d; 253 | this.s2 = s2; 254 | } 255 | } 256 | const Abstract obj = new Class("quux", 33.3, "toto"); 257 | 258 | with(types!Class) { 259 | const type = rtti(obj); 260 | 261 | type.fields[0].get!string(obj).should == "quux"; 262 | type.fields[1].get!string(obj).shouldThrowWithMessage("Cannot get private member"); 263 | type.fields[2].toString(obj).should == "33.3"; 264 | } 265 | } 266 | 267 | 268 | @("fields.byName.get") 269 | @safe unittest { 270 | static class Class { 271 | int i; 272 | double d; 273 | this(int i, double d) { this.i = i; this.d = d; } 274 | } 275 | 276 | const Object obj = new Class(42, 33.3); 277 | 278 | with(types!Class) { 279 | const type = rtti(obj); 280 | 281 | type.field("i").get!int(obj).should == 42; 282 | type.field("i").get!string(obj).shouldThrow; 283 | 284 | type.field("d").get!double(obj).should == 33.3; 285 | type.field("d").get!string(obj).shouldThrow; 286 | 287 | type.field("foo").shouldThrowWithMessage("No field named 'foo'"); 288 | type.field("bar").shouldThrowWithMessage("No field named 'bar'"); 289 | } 290 | } 291 | 292 | 293 | @("fields.byName.set") 294 | @safe unittest { 295 | static class Class { 296 | int i; 297 | double d; 298 | const int const_; 299 | immutable int immutable_; 300 | this(int i, double d) { this.i = i; this.d = d; this.const_ = 77; this.immutable_ = 42; } 301 | } 302 | 303 | Object obj = new Class(42, 33.3); 304 | 305 | with(types!Class) { 306 | const type = rtti(obj); 307 | type.field("i").get!int(obj).should == 42; 308 | 309 | type.field("i").set(obj, 77); 310 | type.field("i").get!int(obj).should == 77; 311 | 312 | type.field("const_").set(obj, 0).shouldThrowWithMessage("Cannot set const member 'const_'"); 313 | type.field("immutable_").set(obj, 0).shouldThrowWithMessage("Cannot set immutable member 'immutable_'"); 314 | } 315 | } 316 | 317 | 318 | @("toString.Int") 319 | @safe unittest { 320 | 321 | static class Int { 322 | int i; 323 | this(int i) { this.i = i; } 324 | 325 | override string toString() @safe pure scope const { 326 | import std.conv: text; 327 | return text(`Int(`, i, `)`); 328 | } 329 | } 330 | 331 | static class Double { 332 | double d; 333 | this(double d) { this.d = d; } 334 | } 335 | 336 | with(types!Int) { 337 | const type = rtti!Int; 338 | type.toString(new Int(42)).should == "Int(42)"; 339 | type.toString(new Int(88)).should == "Int(88)"; 340 | 341 | enum testName = __traits(identifier, __traits(parent, {})); 342 | enum prefix = __MODULE__ ~ "." ~ testName ~ "."; 343 | type.toString(new Double(33.3)).shouldThrowWithMessage( 344 | "Cannot call toString on obj since not of type " ~ prefix ~ "Int"); 345 | } 346 | } 347 | 348 | @("toString.interface") 349 | @safe unittest { 350 | 351 | static interface Interface {} 352 | static class Class: Interface {} 353 | 354 | with(types!Interface) { 355 | const type = rtti!Interface; 356 | enum testId = __traits(identifier, __traits(parent, {})); 357 | enum prefix = __MODULE__ ~ "." ~ testId ~ "."; 358 | type.toString(new Class).shouldThrowWithMessage("Cannot cast non-class type " ~ prefix ~ "Interface"); 359 | } 360 | } 361 | 362 | 363 | @("methods.toString") 364 | @safe unittest { 365 | 366 | import std.algorithm.iteration: map; 367 | import std.array: array; 368 | 369 | static class Arithmetic { 370 | int i; 371 | this(int i) { this.i = i; } 372 | int add(int j) const { return i + j; } 373 | int mul(int j) const { return i * j; } 374 | double toDouble() const { return i; } 375 | } 376 | 377 | const Object obj = new Arithmetic(3); 378 | 379 | with(types!Arithmetic) { 380 | const type = rtti(obj); 381 | type.methods.map!(a => a.toString).array.should == [ 382 | "int add(int)", 383 | "int mul(int)", 384 | "double toDouble()", 385 | ]; 386 | } 387 | } 388 | 389 | 390 | @("methods.byName") 391 | @safe unittest { 392 | 393 | static class Class { 394 | void foo() {} 395 | void bar() {} 396 | } 397 | 398 | const Object obj = new Class; 399 | 400 | with(types!Class) { 401 | const type = rtti(obj); 402 | 403 | const foo = type.method("foo"); 404 | assert(foo is type.methods[0]); 405 | 406 | const bar = type.method("bar"); 407 | assert(bar is type.methods[1]); 408 | 409 | type.method("baz").shouldThrowWithMessage(`No method named 'baz'`); 410 | } 411 | } 412 | 413 | 414 | @("methods.call.happy") 415 | // Method calls can't be guaranteed to be @safe or pure 416 | @system unittest { 417 | 418 | static class Arithmetic { 419 | int i; 420 | this(int i) { this.i = i; } 421 | int addMul(int j, int k) const { return (i + j) * k; } 422 | void set(int i) { this.i = i; } 423 | } 424 | 425 | with(types!Arithmetic) { 426 | const Object obj = new Arithmetic(3); 427 | 428 | const type = rtti(obj); 429 | const addMul = type.method("addMul"); 430 | 431 | addMul.call!int(obj, 1, 2).should == 8; 432 | addMul.call!int(obj, 2, 4).should == 20; 433 | } 434 | 435 | with(types!Arithmetic) { 436 | auto ari = new Arithmetic(3); 437 | Object obj = ari; 438 | 439 | const type = rtti(obj); 440 | const set = type.method("set"); 441 | 442 | ari.i.should == 3; 443 | set.call(obj, 42); 444 | ari.i.should == 42; 445 | } 446 | } 447 | 448 | 449 | @("methods.call.sad") 450 | // Method calls can't be guaranteed to be @safe or pure 451 | @system unittest { 452 | 453 | static class Arithmetic { 454 | int i; 455 | this(int i) { this.i = i; } 456 | int addMul(int j, int k) const { return (i + j) * k; } 457 | void set(int i) { this.i = i; } 458 | } 459 | 460 | with(types!Arithmetic) { 461 | const Object obj = new Arithmetic(3); 462 | const type = rtti(obj); 463 | const set = type.method("set"); 464 | set.call(obj, 42).shouldThrowWithMessage("Cannot call non-const method 'set' on const obj"); 465 | } 466 | 467 | with(types!Arithmetic) { 468 | immutable Object obj = cast(immutable) new Arithmetic(3); 469 | const type = rtti(obj); 470 | const set = type.method("set"); 471 | set.call(obj, 42).shouldThrowWithMessage("Cannot call non-const method 'set' on const obj"); 472 | } 473 | 474 | with(types!Arithmetic) { 475 | Object obj = new Arithmetic(3); 476 | const type = rtti(obj); 477 | const set = type.method("set"); 478 | set.call(obj, 42, 33).shouldThrowWithMessage("'set' takes 1 parameter(s), not 2"); 479 | } 480 | 481 | with(types!Arithmetic) { 482 | static class NotArithmetic {} 483 | Object oops = new NotArithmetic; 484 | const type = rtti(new Arithmetic(3)); 485 | const set = type.method("set"); 486 | set.call(oops, 33).shouldThrowWithMessage("Cannot call 'set' on object not of type Arithmetic"); 487 | } 488 | } 489 | 490 | 491 | @("methods.traits") 492 | @safe unittest { 493 | 494 | static abstract class Abstract { 495 | abstract void lefunc(); 496 | } 497 | 498 | static class Class: Abstract { 499 | final void final_() {} 500 | @safe void safe() {} 501 | @trusted void trusted() {} 502 | @system void system() {} 503 | override void lefunc() {} 504 | static void static_() {} 505 | void twoInts(int i, int j) { } 506 | void threeInts(int i, int j, int k) { } 507 | string sayMyName() const { return "LeClass"; } 508 | } 509 | 510 | const Object obj = new Class; 511 | 512 | with(types!Class) { 513 | const type = rtti(obj); 514 | 515 | type.method("final_").isFinal.should == true; 516 | type.method("safe").isFinal.should == false; 517 | 518 | type.method("lefunc").isOverride.should == true; 519 | type.method("final_").isOverride.should == false; 520 | 521 | type.method("static_").isStatic.should == true; 522 | type.method("final_").isStatic.should == false; 523 | 524 | type.method("safe").isVirtual.should == true; 525 | type.method("final_").isVirtual.should == false; 526 | 527 | type.method("safe").isSafe.should == true; 528 | type.method("trusted").isSafe.should == true; 529 | type.method("system").isSafe.should == false; 530 | 531 | type.method("twoInts").arity.should == 2; 532 | type.method("threeInts").arity.should == 3; 533 | 534 | // FIXME - better way of doing this 535 | type.method("sayMyName").returnType.name.should == "immutable(char)[]"; 536 | 537 | type.method("twoInts").parameters.length.should == 2; 538 | type.method("threeInts").parameters.length.should == 3; 539 | } 540 | } 541 | --------------------------------------------------------------------------------