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 }