]>
Commit | Line | Data |
---|---|---|
03385ed3 | 1 | // Written in the D programming language. |
2 | ||
3 | /** | |
4 | This module implements experimental additions/modifications to $(MREF std, _typecons). | |
5 | ||
6 | Use this module to test out new functionality for $(REF wrap, std, _typecons) | |
7 | which allows for a struct to be wrapped against an interface; the | |
8 | implementation in $(MREF std, _typecons) only allows for classes to use the wrap | |
9 | functionality. | |
10 | ||
11 | Source: $(PHOBOSSRC std/experimental/_typecons.d) | |
12 | ||
13 | Copyright: Copyright the respective authors, 2008- | |
14 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
15 | Authors: $(HTTP erdani.org, Andrei Alexandrescu), | |
16 | $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), | |
17 | Don Clugston, | |
18 | Shin Fujishiro, | |
19 | Kenji Hara | |
20 | */ | |
21 | module std.experimental.typecons; | |
22 | ||
23 | import std.meta; // : AliasSeq, allSatisfy; | |
24 | import std.traits; | |
25 | ||
26 | import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, | |
27 | isImplicitlyConvertible, mixinAll, staticIota, | |
28 | GetOverloadedMethods; | |
29 | ||
30 | private | |
31 | { | |
32 | pragma(mangle, "_d_toObject") | |
33 | extern(C) pure nothrow Object typecons_d_toObject(void* p); | |
34 | } | |
35 | ||
36 | /* | |
37 | * Avoids opCast operator overloading. | |
38 | */ | |
39 | private template dynamicCast(T) | |
40 | if (is(T == class) || is(T == interface)) | |
41 | { | |
42 | @trusted | |
43 | T dynamicCast(S)(inout S source) | |
44 | if (is(S == class) || is(S == interface)) | |
45 | { | |
46 | static if (is(Unqual!S : Unqual!T)) | |
47 | { | |
48 | import std.traits : QualifierOf; | |
49 | alias Qual = QualifierOf!S; // SharedOf or MutableOf | |
50 | alias TmpT = Qual!(Unqual!T); | |
51 | inout(TmpT) tmp = source; // bypass opCast by implicit conversion | |
52 | return *cast(T*)(&tmp); // + variable pointer cast + dereference | |
53 | } | |
54 | else | |
55 | { | |
56 | return cast(T) typecons_d_toObject(*cast(void**)(&source)); | |
57 | } | |
58 | } | |
59 | } | |
60 | ||
61 | @system unittest | |
62 | { | |
63 | class C { @disable opCast(T)() {} } | |
64 | auto c = new C; | |
65 | static assert(!__traits(compiles, cast(Object) c)); | |
66 | auto o = dynamicCast!Object(c); | |
67 | assert(c is o); | |
68 | ||
69 | interface I { @disable opCast(T)() {} Object instance(); } | |
70 | interface J { @disable opCast(T)() {} Object instance(); } | |
71 | class D : I, J { Object instance() { return this; } } | |
72 | I i = new D(); | |
73 | static assert(!__traits(compiles, cast(J) i)); | |
74 | J j = dynamicCast!J(i); | |
75 | assert(i.instance() is j.instance()); | |
76 | } | |
77 | ||
78 | /* | |
79 | * Determines if the `Source` type satisfies all interface requirements of | |
80 | * `Targets`. | |
81 | */ | |
82 | private template implementsInterface(Source, Targets...) | |
83 | if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) | |
84 | { | |
85 | import std.meta : staticMap; | |
86 | ||
87 | // strict upcast | |
88 | bool implementsInterface()() | |
89 | if (Targets.length == 1 && is(Source : Targets[0])) | |
90 | { | |
91 | return true; | |
92 | } | |
93 | // structural upcast | |
94 | template implementsInterface() | |
95 | if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets)) | |
96 | { | |
97 | auto implementsInterface() | |
98 | { | |
99 | return hasRequiredMethods!(); | |
100 | } | |
101 | ||
102 | // list of FuncInfo | |
103 | alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets); | |
104 | // list of function symbols | |
105 | alias SourceMembers = GetOverloadedMethods!Source; | |
106 | ||
107 | // Check whether all of SourceMembers satisfy covariance target in | |
108 | // TargetMembers | |
109 | template hasRequiredMethods(size_t i = 0) | |
110 | { | |
111 | static if (i >= TargetMembers.length) | |
112 | enum hasRequiredMethods = true; | |
113 | else | |
114 | { | |
115 | enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers); | |
22163f0d | 116 | debug |
117 | { | |
118 | static if (foundFunc == -1) | |
119 | pragma(msg, "Could not locate matching function for: ", | |
120 | TargetMembers[i].stringof); | |
121 | } | |
03385ed3 | 122 | enum hasRequiredMethods = |
123 | foundFunc != -1 && | |
124 | hasRequiredMethods!(i + 1); | |
125 | } | |
126 | } | |
127 | } | |
128 | } | |
129 | // ditto | |
130 | private template implementsInterface(Source, Targets...) | |
131 | if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) | |
132 | { | |
133 | import std.meta : staticMap; | |
134 | ||
135 | alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets)); | |
136 | } | |
137 | ||
138 | @safe unittest | |
139 | { | |
140 | interface Foo { | |
141 | void foo(); | |
142 | } | |
143 | interface Bar { | |
144 | void bar(); | |
145 | } | |
146 | interface FooBar : Foo, Bar { | |
147 | void foobar(); | |
148 | } | |
149 | ||
150 | struct A { | |
151 | void foo() {} | |
152 | } | |
153 | struct B { | |
154 | void bar() {} | |
155 | void foobar() {} | |
156 | } | |
157 | class C { | |
158 | void foo() {} | |
159 | void bar() {} | |
160 | } | |
161 | struct D { | |
162 | void foo() {} | |
163 | void bar() {} | |
164 | void foobar() {} | |
165 | } | |
166 | // Implements interface | |
167 | static assert(implementsInterface!(A, Foo)); | |
168 | static assert(implementsInterface!(A, const(Foo))); | |
169 | static assert(implementsInterface!(A, immutable(Foo))); | |
170 | // Doesn't implement interface | |
171 | static assert(!implementsInterface!(B, Foo)); | |
172 | static assert(implementsInterface!(B, Bar)); | |
173 | // Implements both interfaces | |
174 | static assert(implementsInterface!(C, Foo)); | |
175 | static assert(implementsInterface!(C, Bar)); | |
176 | static assert(implementsInterface!(C, Foo, Bar)); | |
177 | static assert(implementsInterface!(C, Foo, const(Bar))); | |
178 | static assert(!implementsInterface!(A, Foo, Bar)); | |
179 | static assert(!implementsInterface!(A, Foo, immutable(Bar))); | |
180 | // Implements inherited | |
181 | static assert(implementsInterface!(D, FooBar)); | |
182 | static assert(!implementsInterface!(B, FooBar)); | |
183 | } | |
184 | ||
185 | private enum isInterface(ConceptType) = is(ConceptType == interface); | |
186 | ||
187 | /// | |
188 | template wrap(Targets...) | |
189 | if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) | |
190 | { | |
191 | import std.meta : ApplyLeft, staticMap; | |
192 | ||
193 | version (StdDdoc) | |
194 | { | |
195 | /** | |
196 | * Wrap src in an anonymous class implementing $(D_PARAM Targets). | |
197 | * | |
198 | * wrap creates an internal wrapper class which implements the | |
199 | * interfaces in `Targets` using the methods of `src`, then returns a | |
200 | * GC-allocated instance of it. | |
201 | * | |
202 | * $(D_PARAM Source) can be either a `class` or a `struct`, but it must | |
203 | * $(I structurally conform) with all the $(D_PARAM Targets) | |
204 | * interfaces; i.e. it must provide concrete methods with compatible | |
205 | * signatures of those in $(D_PARAM Targets). | |
206 | * | |
207 | * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will | |
208 | * create a copy; it is not possible to affect the original `struct` | |
209 | * through the wrapper. | |
210 | * | |
211 | * The returned object additionally supports $(LREF unwrap). | |
212 | * | |
213 | * Note: | |
214 | * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a | |
215 | * class which explicitly implements it, wrap simply returns src | |
216 | * upcasted to `Targets[0]`. | |
217 | * | |
218 | * Bugs: | |
219 | * wrap does not support interfaces which take their own type as either | |
220 | * a parameter type or return type in any of its methods. | |
221 | * | |
222 | * See_Also: $(LREF unwrap) for examples | |
223 | */ | |
224 | auto wrap(Source)(inout Source src) | |
225 | if (implementsInterface!(Source, Targets)); | |
226 | } | |
227 | ||
228 | static if (!allSatisfy!(isMutable, Targets)) | |
229 | alias wrap = .wrap!(staticMap!(Unqual, Targets)); | |
230 | else | |
231 | { | |
232 | // strict upcast | |
233 | auto wrap(Source)(inout Source src) | |
234 | if (Targets.length == 1 && is(Source : Targets[0])) | |
235 | { | |
236 | alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]); | |
237 | return dynamicCast!(inout T)(src); | |
238 | } | |
239 | ||
240 | // structural upcast | |
241 | template wrap(Source) | |
242 | if (!allSatisfy!(ApplyLeft!(isImplicitlyConvertible, Source), Targets)) | |
243 | { | |
244 | auto wrap(inout Source src) | |
245 | { | |
246 | static assert(implementsInterface!(Source, Targets), | |
247 | "Source "~Source.stringof~ | |
248 | " does not have structural conformance to "~ | |
249 | Targets.stringof); | |
250 | ||
251 | alias T = Select!(is(Source == shared), shared Impl, Impl); | |
252 | return new inout T(src); | |
253 | } | |
254 | ||
255 | // list of FuncInfo | |
256 | alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets)); | |
257 | // list of function symbols | |
258 | alias SourceMembers = GetOverloadedMethods!Source; | |
259 | ||
260 | static if (is(Source == class) || is(Source == interface)) | |
261 | alias StructuralType = Object; | |
262 | else static if (is(Source == struct)) | |
263 | alias StructuralType = Source; | |
264 | ||
265 | // Check whether all of SourceMembers satisfy covariance target in TargetMembers | |
266 | // Internal wrapper class | |
267 | final class Impl : Structural!StructuralType, Targets | |
268 | { | |
269 | private: | |
270 | Source _wrap_source; | |
271 | ||
272 | this( inout Source s) inout @safe pure nothrow { _wrap_source = s; } | |
273 | this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; } | |
274 | ||
275 | static if (is(Source == class) || is(Source == interface)) | |
276 | { | |
277 | // BUG: making private should work with NVI. | |
278 | protected inout(Object) _wrap_getSource() inout @safe | |
279 | { | |
280 | return dynamicCast!(inout Object)(_wrap_source); | |
281 | } | |
282 | } | |
283 | else | |
284 | { | |
285 | // BUG: making private should work with NVI. | |
286 | protected inout(Source) _wrap_getSource() inout @safe | |
287 | { | |
288 | return _wrap_source; | |
289 | } | |
290 | } | |
291 | ||
292 | import std.conv : to; | |
293 | import std.functional : forward; | |
294 | template generateFun(size_t i) | |
295 | { | |
296 | enum name = TargetMembers[i].name; | |
297 | enum fa = functionAttributes!(TargetMembers[i].type); | |
298 | static args(int num)() | |
299 | { | |
300 | string r; | |
301 | bool first = true; | |
302 | foreach (i; staticIota!(0, num)) | |
303 | { | |
304 | import std.conv : to; | |
305 | r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string; | |
306 | first = false; | |
307 | } | |
308 | return r; | |
309 | } | |
310 | static if (fa & FunctionAttribute.property) | |
311 | { | |
312 | static if (Parameters!(TargetMembers[i].type).length == 0) | |
313 | enum fbody = "_wrap_source."~name; | |
314 | else | |
315 | enum fbody = "_wrap_source."~name~" = a1"; | |
316 | } | |
317 | else | |
318 | { | |
319 | enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")"; | |
320 | } | |
321 | enum generateFun = | |
322 | "override "~wrapperSignature!(TargetMembers[i]) ~ | |
323 | "{ return "~fbody~"; }"; | |
324 | } | |
325 | ||
326 | public: | |
327 | mixin mixinAll!( | |
328 | staticMap!(generateFun, staticIota!(0, TargetMembers.length))); | |
329 | } | |
330 | } | |
331 | } | |
332 | } | |
333 | ||
334 | // Build a signature that matches the provided function | |
335 | // Each argument will be provided a name in the form a# | |
336 | private template wrapperSignature(alias fun) | |
337 | { | |
338 | enum name = fun.name; | |
339 | enum fa = functionAttributes!(fun.type); | |
340 | static @property stc() | |
341 | { | |
342 | string r; | |
343 | if (fa & FunctionAttribute.property) r ~= "@property "; | |
344 | if (fa & FunctionAttribute.ref_) r ~= "ref "; | |
345 | if (fa & FunctionAttribute.pure_) r ~= "pure "; | |
346 | if (fa & FunctionAttribute.nothrow_) r ~= "nothrow "; | |
347 | if (fa & FunctionAttribute.trusted) r ~= "@trusted "; | |
348 | if (fa & FunctionAttribute.safe) r ~= "@safe "; | |
349 | return r; | |
350 | } | |
351 | static @property mod() | |
352 | { | |
353 | alias type = AliasSeq!(fun.type)[0]; | |
354 | string r; | |
355 | static if (is(type == immutable)) r ~= " immutable"; | |
356 | else | |
357 | { | |
358 | static if (is(type == shared)) r ~= " shared"; | |
359 | static if (is(type == const)) r ~= " const"; | |
360 | else static if (is(type == inout)) r ~= " inout"; | |
361 | //else --> mutable | |
362 | } | |
363 | return r; | |
364 | } | |
365 | alias param = Parameters!(fun.type); | |
366 | static @property wrapperParameters() | |
367 | { | |
368 | string r; | |
369 | bool first = true; | |
370 | foreach (i, p; param) | |
371 | { | |
372 | import std.conv : to; | |
373 | r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string; | |
374 | first = false; | |
375 | } | |
376 | return r; | |
377 | } | |
378 | ||
379 | enum wrapperSignature = | |
380 | stc~ReturnType!(fun.type).stringof ~ " " | |
381 | ~ name~"("~wrapperParameters~")"~mod; | |
382 | } | |
383 | ||
384 | @safe unittest | |
385 | { | |
386 | interface M | |
387 | { | |
388 | void f1(); | |
389 | void f2(string[] args, int count); | |
390 | void f3(string[] args, int count) pure const; | |
391 | } | |
392 | ||
393 | alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M); | |
394 | static assert(wrapperSignature!(TargetMembers[0]) == "void f1()" | |
395 | , wrapperSignature!(TargetMembers[0])); | |
396 | ||
397 | static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)" | |
398 | , wrapperSignature!(TargetMembers[1])); | |
399 | ||
400 | static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const" | |
401 | , wrapperSignature!(TargetMembers[2])); | |
402 | } | |
403 | ||
404 | // Internal class to support dynamic cross-casting | |
405 | private interface Structural(T) | |
406 | { | |
407 | inout(T) _wrap_getSource() inout @safe pure nothrow; | |
408 | } | |
409 | ||
410 | private string unwrapExceptionText(Source, Target)() | |
411 | { | |
412 | return Target.stringof~ " not wrapped into "~ Source.stringof; | |
413 | } | |
414 | ||
415 | version (StdDdoc) | |
416 | { | |
417 | /** | |
418 | * Extract object previously wrapped by $(LREF wrap). | |
419 | * | |
420 | * Params: | |
421 | * Target = type of wrapped object | |
422 | * src = wrapper object returned by $(LREF wrap) | |
423 | * | |
424 | * Returns: the wrapped object, or null if src is not a wrapper created | |
425 | * by $(LREF wrap) and $(D_PARAM Target) is a class | |
426 | * | |
427 | * Throws: $(REF ConvException, std, conv) when attempting to extract a | |
428 | * struct which is not the wrapped type | |
429 | * | |
430 | * See_also: $(LREF wrap) | |
431 | */ | |
432 | public inout(Target) unwrap(Target, Source)(inout Source src); | |
433 | } | |
434 | ||
435 | /// | |
436 | @system unittest | |
437 | { | |
438 | interface Quack | |
439 | { | |
440 | int quack(); | |
441 | @property int height(); | |
442 | } | |
443 | interface Flyer | |
444 | { | |
445 | @property int height(); | |
446 | } | |
447 | class Duck : Quack | |
448 | { | |
449 | int quack() { return 1; } | |
450 | @property int height() { return 10; } | |
451 | } | |
452 | class Human | |
453 | { | |
454 | int quack() { return 2; } | |
455 | @property int height() { return 20; } | |
456 | } | |
457 | struct HumanStructure | |
458 | { | |
459 | int quack() { return 3; } | |
460 | @property int height() { return 30; } | |
461 | } | |
462 | ||
463 | Duck d1 = new Duck(); | |
464 | Human h1 = new Human(); | |
465 | HumanStructure hs1; | |
466 | ||
467 | interface Refreshable | |
468 | { | |
469 | int refresh(); | |
470 | } | |
471 | // does not have structural conformance | |
472 | static assert(!__traits(compiles, d1.wrap!Refreshable)); | |
473 | static assert(!__traits(compiles, h1.wrap!Refreshable)); | |
474 | static assert(!__traits(compiles, hs1.wrap!Refreshable)); | |
475 | ||
476 | // strict upcast | |
477 | Quack qd = d1.wrap!Quack; | |
478 | assert(qd is d1); | |
479 | assert(qd.quack() == 1); // calls Duck.quack | |
480 | // strict downcast | |
481 | Duck d2 = qd.unwrap!Duck; | |
482 | assert(d2 is d1); | |
483 | ||
484 | // structural upcast | |
485 | Quack qh = h1.wrap!Quack; | |
486 | Quack qhs = hs1.wrap!Quack; | |
487 | assert(qh.quack() == 2); // calls Human.quack | |
488 | assert(qhs.quack() == 3); // calls HumanStructure.quack | |
489 | // structural downcast | |
490 | Human h2 = qh.unwrap!Human; | |
491 | HumanStructure hs2 = qhs.unwrap!HumanStructure; | |
492 | assert(h2 is h1); | |
493 | assert(hs2 is hs1); | |
494 | ||
495 | // structural upcast (two steps) | |
496 | Quack qx = h1.wrap!Quack; // Human -> Quack | |
497 | Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack | |
498 | Flyer fx = qx.wrap!Flyer; // Quack -> Flyer | |
499 | Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer | |
500 | assert(fx.height == 20); // calls Human.height | |
501 | assert(fxs.height == 30); // calls HumanStructure.height | |
502 | // strucural downcast (two steps) | |
503 | Quack qy = fx.unwrap!Quack; // Flyer -> Quack | |
504 | Quack qys = fxs.unwrap!Quack; // Flyer -> Quack | |
505 | Human hy = qy.unwrap!Human; // Quack -> Human | |
506 | HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure | |
507 | assert(hy is h1); | |
508 | assert(hys is hs1); | |
509 | // strucural downcast (one step) | |
510 | Human hz = fx.unwrap!Human; // Flyer -> Human | |
511 | HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure | |
512 | assert(hz is h1); | |
513 | assert(hzs is hs1); | |
514 | } | |
515 | ||
516 | /// | |
517 | @system unittest | |
518 | { | |
519 | import std.traits : functionAttributes, FunctionAttribute; | |
520 | interface A { int run(); } | |
521 | interface B { int stop(); @property int status(); } | |
522 | class X | |
523 | { | |
524 | int run() { return 1; } | |
525 | int stop() { return 2; } | |
526 | @property int status() { return 3; } | |
527 | } | |
528 | ||
529 | auto x = new X(); | |
530 | auto ab = x.wrap!(A, B); | |
531 | A a = ab; | |
532 | B b = ab; | |
533 | assert(a.run() == 1); | |
534 | assert(b.stop() == 2); | |
535 | assert(b.status == 3); | |
536 | static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property); | |
537 | } | |
538 | ||
539 | template unwrap(Target) | |
540 | { | |
541 | static if (!isMutable!Target) | |
542 | alias unwrap = .unwrap!(Unqual!Target); | |
543 | else | |
544 | { | |
545 | // strict downcast | |
546 | auto unwrap(Source)(inout Source src) | |
547 | if (is(Target : Source)) | |
548 | { | |
549 | alias T = Select!(is(Source == shared), shared Target, Target); | |
550 | return dynamicCast!(inout T)(src); | |
551 | } | |
552 | ||
553 | // structural downcast for struct target | |
554 | auto unwrap(Source)(inout Source src) | |
555 | if (is(Target == struct)) | |
556 | { | |
557 | alias T = Select!(is(Source == shared), shared Target, Target); | |
558 | auto upCastSource = dynamicCast!Object(src); // remove qualifier | |
559 | do | |
560 | { | |
561 | if (auto a = dynamicCast!(Structural!Object)(upCastSource)) | |
562 | { | |
563 | upCastSource = a._wrap_getSource(); | |
564 | } | |
565 | else if (auto a = dynamicCast!(Structural!T)(upCastSource)) | |
566 | { | |
567 | return a._wrap_getSource(); | |
568 | } | |
569 | else | |
570 | { | |
571 | static if (hasMember!(Source, "_wrap_getSource")) | |
572 | return unwrap!Target(src._wrap_getSource()); | |
573 | else | |
574 | break; | |
575 | } | |
576 | } while (upCastSource); | |
577 | import std.conv : ConvException; | |
578 | throw new ConvException(unwrapExceptionText!(Source,Target)); | |
579 | } | |
580 | // structural downcast for class target | |
581 | auto unwrap(Source)(inout Source src) | |
582 | if (!is(Target : Source) && !is(Target == struct)) | |
583 | { | |
584 | alias T = Select!(is(Source == shared), shared Target, Target); | |
585 | Object upCastSource = dynamicCast!(Object)(src); // remove qualifier | |
586 | do | |
587 | { | |
588 | // Unwrap classes | |
589 | if (auto a = dynamicCast!(Structural!Object)(upCastSource)) | |
590 | { | |
591 | if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource())) | |
592 | return d; | |
593 | } | |
594 | // Unwrap a structure of type T | |
595 | else if (auto a = dynamicCast!(Structural!T)(upCastSource)) | |
596 | { | |
597 | return a._wrap_getSource(); | |
598 | } | |
599 | // Unwrap class that already inherited from interface | |
600 | else if (auto d = dynamicCast!(inout T)(upCastSource)) | |
601 | { | |
602 | return d; | |
603 | } | |
604 | // Recurse to find the struct Target within a wrapped tree | |
605 | else | |
606 | { | |
607 | static if (hasMember!(Source, "_wrap_getSource")) | |
608 | return unwrap!Target(src._wrap_getSource()); | |
609 | else | |
610 | break; | |
611 | } | |
612 | } while (upCastSource); | |
613 | return null; | |
614 | } | |
615 | } | |
616 | } | |
617 | ||
618 | @system unittest | |
619 | { | |
620 | // Validate const/immutable | |
621 | class A | |
622 | { | |
623 | int draw() { return 1; } | |
624 | int draw(int v) { return v; } | |
625 | ||
626 | int draw() const { return 2; } | |
627 | int draw() shared { return 3; } | |
628 | int draw() shared const { return 4; } | |
629 | int draw() immutable { return 5; } | |
630 | } | |
631 | interface Drawable | |
632 | { | |
633 | int draw(); | |
634 | int draw() const; | |
635 | int draw() shared; | |
636 | int draw() shared const; | |
637 | int draw() immutable; | |
638 | } | |
639 | interface Drawable2 | |
640 | { | |
641 | int draw(int v); | |
642 | } | |
643 | ||
644 | auto ma = new A(); | |
645 | auto sa = new shared A(); | |
646 | auto ia = new immutable A(); | |
647 | { | |
648 | Drawable md = ma.wrap!Drawable; | |
649 | const Drawable cd = ma.wrap!Drawable; | |
650 | shared Drawable sd = sa.wrap!Drawable; | |
651 | shared const Drawable scd = sa.wrap!Drawable; | |
652 | immutable Drawable id = ia.wrap!Drawable; | |
653 | assert( md.draw() == 1); | |
654 | assert( cd.draw() == 2); | |
655 | assert( sd.draw() == 3); | |
656 | assert(scd.draw() == 4); | |
657 | assert( id.draw() == 5); | |
658 | } | |
659 | { | |
660 | Drawable2 d = ma.wrap!Drawable2; | |
661 | static assert(!__traits(compiles, d.draw())); | |
662 | assert(d.draw(10) == 10); | |
663 | } | |
664 | } | |
665 | @system unittest | |
666 | { | |
667 | // Bugzilla 10377 | |
668 | import std.algorithm, std.range; | |
669 | ||
670 | interface MyInputRange(T) | |
671 | { | |
672 | @property T front(); | |
673 | void popFront(); | |
674 | @property bool empty(); | |
675 | } | |
676 | ||
677 | //auto o = iota(0,10,1).inputRangeObject(); | |
678 | //pragma(msg, __traits(allMembers, typeof(o))); | |
679 | auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); | |
680 | assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); | |
681 | } | |
682 | @system unittest | |
683 | { | |
684 | // Bugzilla 10536 | |
685 | interface Interface | |
686 | { | |
687 | int foo(); | |
688 | } | |
689 | class Pluggable | |
690 | { | |
691 | int foo() { return 1; } | |
692 | @disable void opCast(T, this X)(); // ! | |
693 | } | |
694 | ||
695 | Interface i = new Pluggable().wrap!Interface; | |
696 | assert(i.foo() == 1); | |
697 | } | |
698 | @system unittest | |
699 | { | |
700 | // Enhancement 10538 | |
701 | interface Interface | |
702 | { | |
703 | int foo(); | |
704 | int bar(int); | |
705 | } | |
706 | class Pluggable | |
707 | { | |
708 | int opDispatch(string name, A...)(A args) { return 100; } | |
709 | } | |
710 | ||
711 | Interface i = wrap!Interface(new Pluggable()); | |
712 | assert(i.foo() == 100); | |
713 | assert(i.bar(10) == 100); | |
714 | } | |
715 | ||
716 | // Concat all Targets function members into one tuple | |
717 | private template ConcatInterfaceMembers(Targets...) | |
718 | { | |
719 | static if (Targets.length == 0) | |
720 | alias ConcatInterfaceMembers = AliasSeq!(); | |
721 | else static if (Targets.length == 1) | |
722 | alias ConcatInterfaceMembers | |
723 | = AliasSeq!(GetOverloadedMethods!(Targets[0])); | |
724 | else | |
725 | alias ConcatInterfaceMembers = AliasSeq!( | |
726 | GetOverloadedMethods!(Targets[0]), | |
727 | ConcatInterfaceMembers!(Targets[1..$])); | |
728 | } | |
729 | // Remove duplicated functions based on the identifier name and function type covariance | |
730 | private template UniqMembers(members...) | |
731 | { | |
732 | template FuncInfo(string s, F) | |
733 | { | |
734 | enum name = s; | |
735 | alias type = F; | |
736 | } | |
737 | ||
738 | static if (members.length == 0) | |
739 | alias UniqMembers = AliasSeq!(); | |
740 | else | |
741 | { | |
742 | alias func = members[0]; | |
743 | enum name = __traits(identifier, func); | |
744 | alias type = FunctionTypeOf!func; | |
745 | template check(size_t i, mem...) | |
746 | { | |
747 | static if (i >= mem.length) | |
748 | enum ptrdiff_t check = -1; | |
749 | else static if | |
750 | (__traits(identifier, func) == __traits(identifier, mem[i]) && | |
751 | !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void)) | |
752 | { | |
753 | enum ptrdiff_t check = i; | |
754 | } | |
755 | else | |
756 | enum ptrdiff_t check = check!(i + 1, mem); | |
757 | } | |
758 | enum ptrdiff_t x = 1 + check!(0, members[1 .. $]); | |
759 | static if (x >= 1) | |
760 | { | |
761 | alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x])); | |
762 | alias remain = UniqMembers!(members[1 .. x], members[x + 1 .. $]); | |
763 | ||
764 | static if (remain.length >= 1 && remain[0].name == name && | |
765 | !is(DerivedFunctionType!(typex, remain[0].type) == void)) | |
766 | { | |
767 | alias F = DerivedFunctionType!(typex, remain[0].type); | |
768 | alias UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); | |
769 | } | |
770 | else | |
771 | alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain); | |
772 | } | |
773 | else | |
774 | { | |
775 | alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $])); | |
776 | } | |
777 | } | |
778 | } | |
779 | ||
780 | // find a function from Fs that has same identifier and covariant type with f | |
781 | private template findCovariantFunction(alias finfo, Source, Fs...) | |
782 | { | |
783 | template check(size_t i = 0) | |
784 | { | |
785 | static if (i >= Fs.length) | |
786 | enum ptrdiff_t check = -1; | |
787 | else | |
788 | { | |
789 | enum ptrdiff_t check = | |
790 | (finfo.name == __traits(identifier, Fs[i])) && | |
791 | isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type) | |
792 | ? i : check!(i + 1); | |
793 | } | |
794 | } | |
795 | enum x = check!(); | |
796 | static if (x == -1 && is(typeof(Source.opDispatch))) | |
797 | { | |
798 | alias Params = Parameters!(finfo.type); | |
799 | enum ptrdiff_t findCovariantFunction = | |
800 | is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) || | |
801 | is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) || | |
802 | is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) || | |
803 | is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) || | |
804 | is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init))) | |
805 | ? ptrdiff_t.max : -1; | |
806 | } | |
807 | else | |
808 | enum ptrdiff_t findCovariantFunction = x; | |
809 | } | |
810 | ||
811 | /** | |
812 | Type constructor for final (aka head-const) variables. | |
813 | ||
814 | Final variables cannot be directly mutated or rebound, but references | |
815 | reached through the variable are typed with their original mutability. | |
816 | It is equivalent to `final` variables in D1 and Java, as well as | |
817 | `readonly` variables in C#. | |
818 | ||
819 | When `T` is a `const` or `immutable` type, `Final` aliases | |
820 | to `T`. | |
821 | */ | |
822 | template Final(T) | |
823 | { | |
824 | static if (is(T == const) || is(T == immutable)) | |
825 | alias Final = T; | |
826 | else | |
827 | { | |
828 | struct Final | |
829 | { | |
830 | import std.typecons : Proxy; | |
831 | ||
832 | private T final_value; | |
833 | mixin Proxy!final_value; | |
834 | ||
835 | /** | |
836 | * Construction is forwarded to the underlying type. | |
837 | */ | |
838 | this(T other) | |
839 | { | |
840 | this.final_value = other; | |
841 | } | |
842 | ||
843 | /// Ditto | |
844 | this(Args...)(auto ref Args args) | |
845 | if (__traits(compiles, T(args))) | |
846 | { | |
847 | static assert((!is(T == struct) && !is(T == union)) || !isNested!T, | |
848 | "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~ | |
849 | "constructed explicitly at the call-site (e.g. auto s = " ~ | |
850 | "makeFinal(" ~ T.stringof ~ "(...));)"); | |
851 | this.final_value = T(args); | |
852 | } | |
853 | ||
854 | // Attaching function attributes gives less noisy error messages | |
855 | pure nothrow @safe @nogc | |
856 | { | |
857 | /++ | |
858 | + All operators, including member access, are forwarded to the | |
859 | + underlying value of type `T` except for these mutating operators, | |
860 | + which are disabled. | |
861 | +/ | |
862 | void opAssign(Other)(Other other) | |
863 | { | |
864 | static assert(0, typeof(this).stringof ~ | |
865 | " cannot be reassigned."); | |
866 | } | |
867 | ||
868 | /// Ditto | |
869 | void opOpAssign(string op, Other)(Other other) | |
870 | { | |
871 | static assert(0, typeof(this).stringof ~ | |
872 | " cannot be reassigned."); | |
873 | } | |
874 | ||
875 | /// Ditto | |
876 | void opUnary(string op : "--")() | |
877 | { | |
878 | static assert(0, typeof(this).stringof ~ | |
879 | " cannot be mutated."); | |
880 | } | |
881 | ||
882 | /// Ditto | |
883 | void opUnary(string op : "++")() | |
884 | { | |
885 | static assert(0, typeof(this).stringof ~ | |
886 | " cannot be mutated."); | |
887 | } | |
888 | } | |
889 | ||
890 | /** | |
891 | * | |
892 | * `Final!T` implicitly converts to an rvalue of type `T` through | |
893 | * `AliasThis`. | |
894 | */ | |
895 | inout(T) final_get() inout | |
896 | { | |
897 | return final_value; | |
898 | } | |
899 | ||
900 | /// Ditto | |
901 | alias final_get this; | |
902 | ||
903 | /// Ditto | |
904 | auto ref opUnary(string op)() | |
905 | if (__traits(compiles, mixin(op ~ "T.init"))) | |
906 | { | |
907 | return mixin(op ~ "this.final_value"); | |
908 | } | |
909 | } | |
910 | } | |
911 | } | |
912 | ||
913 | /// Ditto | |
914 | Final!T makeFinal(T)(T t) | |
915 | { | |
916 | return Final!T(t); | |
917 | } | |
918 | ||
919 | /// `Final` can be used to create class references which cannot be rebound: | |
920 | pure nothrow @safe unittest | |
921 | { | |
922 | static class A | |
923 | { | |
924 | int i; | |
925 | ||
926 | this(int i) pure nothrow @nogc @safe | |
927 | { | |
928 | this.i = i; | |
929 | } | |
930 | } | |
931 | ||
932 | auto a = makeFinal(new A(42)); | |
933 | assert(a.i == 42); | |
934 | ||
935 | //a = new A(24); // Reassignment is illegal, | |
936 | a.i = 24; // But fields are still mutable. | |
937 | ||
938 | assert(a.i == 24); | |
939 | } | |
940 | ||
941 | /// `Final` can also be used to create read-only data fields without using transitive immutability: | |
942 | pure nothrow @safe unittest | |
943 | { | |
944 | static class A | |
945 | { | |
946 | int i; | |
947 | ||
948 | this(int i) pure nothrow @nogc @safe | |
949 | { | |
950 | this.i = i; | |
951 | } | |
952 | } | |
953 | ||
954 | static class B | |
955 | { | |
956 | Final!A a; | |
957 | ||
958 | this(A a) pure nothrow @nogc @safe | |
959 | { | |
960 | this.a = a; // Construction, thus allowed. | |
961 | } | |
962 | } | |
963 | ||
964 | auto b = new B(new A(42)); | |
965 | assert(b.a.i == 42); | |
966 | ||
967 | // b.a = new A(24); // Reassignment is illegal, | |
968 | b.a.i = 24; // but `a` is still mutable. | |
969 | ||
970 | assert(b.a.i == 24); | |
971 | } | |
972 | ||
973 | pure nothrow @safe unittest | |
974 | { | |
975 | static class A { int i; } | |
976 | static assert(!is(Final!A == A)); | |
977 | static assert(is(Final!(const A) == const A)); | |
978 | static assert(is(Final!(immutable A) == immutable A)); | |
979 | ||
980 | Final!A a = new A; | |
981 | static assert(!__traits(compiles, a = new A)); | |
982 | ||
983 | static void foo(ref A a) pure nothrow @safe @nogc {} | |
984 | static assert(!__traits(compiles, foo(a))); | |
985 | ||
986 | assert(a.i == 0); | |
987 | a.i = 42; | |
988 | assert(a.i == 42); | |
989 | ||
990 | Final!int i = 42; | |
991 | static assert(!__traits(compiles, i = 24)); | |
992 | static assert(!__traits(compiles, --i)); | |
993 | static assert(!__traits(compiles, ++i)); | |
994 | assert(i == 42); | |
995 | int iCopy = i; | |
996 | assert(iCopy == 42); | |
997 | iCopy = -i; // non-mutating unary operators must work | |
998 | assert(iCopy == -42); | |
999 | ||
1000 | static struct S | |
1001 | { | |
1002 | int i; | |
1003 | ||
1004 | pure nothrow @safe @nogc: | |
1005 | this(int i){} | |
1006 | this(string s){} | |
1007 | this(int i, string s, float f){ this.i = i; } | |
1008 | } | |
1009 | ||
1010 | Final!S sint = 42; | |
1011 | Final!S sstr = "foo"; | |
1012 | static assert(!__traits(compiles, sint = sstr)); | |
1013 | ||
1014 | auto sboth = Final!S(42, "foo", 3.14); | |
1015 | assert(sboth.i == 42); | |
1016 | ||
1017 | sboth.i = 24; | |
1018 | assert(sboth.i == 24); | |
1019 | ||
1020 | struct NestedS | |
1021 | { | |
1022 | int i; | |
1023 | int get() pure nothrow @safe @nogc { return sboth.i + i; } | |
1024 | } | |
1025 | ||
1026 | // Nested structs must be constructed at the call-site | |
1027 | static assert(!__traits(compiles, Final!NestedS(6))); | |
1028 | auto s = makeFinal(NestedS(6)); | |
1029 | assert(s.i == 6); | |
1030 | assert(s.get == 30); | |
1031 | ||
1032 | class NestedC | |
1033 | { | |
1034 | int i; | |
1035 | ||
1036 | pure nothrow @safe @nogc: | |
1037 | this(int i) { this.i = i; } | |
1038 | int get() { return sboth.i + i; } | |
1039 | } | |
1040 | ||
1041 | auto c = makeFinal(new NestedC(6)); | |
1042 | assert(c.i == 6); | |
1043 | assert(c.get == 30); | |
1044 | } | |
1045 | ||
1046 | pure nothrow @safe unittest | |
1047 | { | |
1048 | auto arr = makeFinal([1, 2, 3]); | |
1049 | static assert(!__traits(compiles, arr = null)); | |
1050 | static assert(!__traits(compiles, arr ~= 4)); | |
1051 | assert((arr ~ 4) == [1, 2, 3, 4]); | |
1052 | } | |
1053 | ||
1054 | // issue 17270 | |
1055 | pure nothrow @nogc @system unittest | |
1056 | { | |
1057 | int i = 1; | |
1058 | Final!(int*) fp = &i; | |
1059 | assert(*fp == 1); | |
1060 | static assert(!__traits(compiles, | |
1061 | fp = &i // direct assignment | |
1062 | )); | |
1063 | static assert(is(typeof(*fp) == int)); | |
1064 | *fp = 2; // indirect assignment | |
1065 | assert(*fp == 2); | |
1066 | int* p = fp; | |
1067 | assert(*p == 2); | |
1068 | } | |
1069 | ||
1070 | pure nothrow @system unittest | |
1071 | { | |
1072 | Final!(int[]) arr; | |
1073 | // static assert(!__traits(compiles, | |
1074 | // arr.length = 10; // bug! | |
1075 | // )); | |
1076 | static assert(!__traits(compiles, | |
1077 | arr.ptr = null | |
1078 | )); | |
1079 | static assert(!__traits(compiles, | |
1080 | arr.ptr++ | |
1081 | )); | |
1082 | } |