1 /++ 2 Argument-dependent lookup for extension methods. 3 4 `addle` lets you extend types with UFCS methods, and share those methods 5 seamlessly with code in other modules. 6 7 License: MIT 8 Authors: Paul Backus 9 +/ 10 module addle; 11 12 version(none) 13 /// $(H3 Adding range primitives to a type) 14 unittest { 15 import addle; 16 import std.range; 17 18 // Import a type from another module 19 import mylib: MyStruct; 20 21 // Define range primitives for MyStruct 22 bool empty(MyStruct a) { return false; } 23 string front(MyStruct a) { return "ok"; } 24 void popFront(MyStruct a) {} 25 26 // MyStruct isn't considered an input range, because 27 // std.range can't see our UFCS methods. 28 static assert(isInputRange!MyStruct == false); 29 30 // ...but extending it makes those methods visible. 31 static assert(isInputRange!(Extended!MyStruct)); 32 33 void main() 34 { 35 import std.range: take, only; 36 import std.algorithm: equal; 37 38 MyStruct myStruct; 39 40 // Now we can use all of the standard range algorithms 41 assert( 42 myStruct.extended 43 .take(3) 44 .equal(only("ok", "ok", "ok")) 45 ); 46 } 47 } 48 49 import std.traits: moduleName; 50 51 // Inline import 52 private template from(string module_) 53 { 54 mixin("import from = ", module_, ";"); 55 } 56 57 /** 58 * Extends a method to include UFCS functions in the calling context. 59 * 60 * When calling an extended method of an object, the following locations are 61 * searched, in order, for a method or UFCS function with the given name: 62 * 63 * $(NUMBERED_LIST 64 * * The object itself. 65 * * The module where the object's type is defined. 66 * * The `context` module (by default, the module that contains the 67 * extended method call). 68 * ) 69 * 70 * If no method or function is found, a compile-time error is generated. 71 * 72 * Params: 73 * method = the name of the method to call 74 * context = the name of the module to search for extended methods 75 */ 76 template extendedMethod(string method, string context = __MODULE__) 77 { 78 /** 79 * Calls the extended method. 80 * 81 * Params: 82 * obj = the object whose extended method is being called 83 * args = arguments to the extended method 84 * 85 * Returns: the return value of the extended method. 86 */ 87 auto ref extendedMethod(T, Args...)(auto ref T obj, auto ref Args args) 88 { 89 import core.lifetime: forward; 90 91 static if (__traits(compiles, mixin("obj.", method, "(forward!args)"))) { 92 // Normal method 93 return mixin("obj.", method, "(forward!args)"); 94 } else static if (__traits(compiles, 95 __traits(getMember, from!(moduleName!T), method)(forward!(obj, args)) 96 )) { 97 // UFCS method from defining module 98 return __traits(getMember, from!(moduleName!T), method)(forward!(obj, args)); 99 } else static if (__traits(compiles, 100 __traits(getMember, from!context, method)(forward!(obj, args)), 101 )) { 102 // UFCS method from extending module 103 return __traits(getMember, from!context, method)(forward!(obj, args)); 104 } else { 105 import std.traits: fullyQualifiedName; 106 107 static assert(false, 108 "no extended method `" ~ method ~ "` found for type `" 109 ~ fullyQualifiedName!T ~ "` in module `" ~ context ~ "`" 110 ); 111 } 112 } 113 } 114 115 /** 116 * A wrapper that allows [extendedMethod|extended methods] to be called as 117 * though they were regular methods. 118 * 119 * Params: 120 * T = the type of the wrapped object. 121 * context = the `context` module to be used for extended method lookup. 122 */ 123 struct Extended(T, string context = __MODULE__) 124 { 125 import std.meta: staticIndexOf; 126 127 /// The wrapped object. 128 T obj; 129 130 /// Implicitly converts to the wrapped object. 131 alias obj this; 132 133 /** 134 * Forwards all method calls to [extendedMethod]. 135 * 136 * Params: 137 * args = arguments to pass to the extended method. 138 * 139 * Returns: the return value of the extended method 140 */ 141 auto ref opDispatch(string method, Args...)(auto ref Args args) 142 { 143 import core.lifetime: forward; 144 145 return obj.extendedMethod!(method, context)(forward!args); 146 } 147 } 148 149 /** 150 * Creates an [Extended] wrapper around an object. 151 */ 152 template extended(string context = __MODULE__) 153 { 154 /// 155 inout(Extended!(T, context)) extended(T)(auto ref inout(T) obj) 156 { 157 import core.lifetime: forward; 158 159 return inout(Extended!(T, context))(obj); 160 } 161 }