1 module localimport;
2 
3 /**
4  * Check if string can be used as a straight module import.
5  */
6 private template IsModuleImport(string import_) {
7     enum IsModuleImport = __traits(compiles, { mixin("import ", import_, ";"); });
8 }
9 
10 /**
11  * Check if the requested symbol is found in the specified module.
12  */
13 private template IsSymbolInModule(string module_, string symbol) {
14     static if (IsModuleImport!module_) {
15         enum import_ = module_ ~ ":" ~ symbol;
16         enum IsSymbolInModule = __traits(compiles, {
17                 mixin("import ", import_, ";");
18             });
19     } else {
20         enum IsSymbolInModule = false;
21     }
22 }
23 
24 /**
25  * Used to generate useful error messages.
26  */
27 private template failedSymbol(string symbol, string module_) {
28     void failedSymbol(Args...)(auto ref Args args) {
29         enum msg = "Symbol \"" ~ symbol ~ "\" not found in " ~ module_;
30         version (LocalImportUnittest) {
31             throw new Exception(msg);
32         } else {
33             static assert(0, msg);
34         }
35     }
36 }
37 
38 /**
39  * Here the magic happens.
40  * Recursively descends along the dots in usage until it can resolve an imported
41  * module or symbol from module or reaches the end of the dot chain.
42  */
43 private struct FromImpl(string module_) {
44     // opDispatch handles access to missing members of an object. 
45     template opDispatch(string symbol) {
46         // statically check if symbol is in given module
47         static if (IsSymbolInModule!(module_, symbol)) {
48             // Symbol import: emit module import and alias opDispatch to
49             // the symbol.
50             mixin("import ", module_, "; alias opDispatch = ", symbol, ";");
51         } else { // symbol not in module
52             static if (module_.length == 0) {
53                 // module string is of zero length, import of a top level module.
54                 enum opDispatch = FromImpl!(symbol)();
55             } else {
56                 // Check if we have a full module import.
57                 enum import_ = module_ ~ "." ~ symbol;
58                 static if (IsModuleImport!import_) {
59                     // full module import
60                     enum opDispatch = FromImpl!(import_)();
61                 } else {
62                     // failed symbol import as well as full module import and
63                     // the last symbol is of nonzero length.
64                     // -> resolution failed!
65                     alias opDispatch = failedSymbol!(symbol, module_);
66                 }
67             }
68         }
69     }
70 }
71 
72 /**
73  * Used as entry point for local imports.
74  */
75 enum from = FromImpl!null();
76 
77 /**
78  * Test things that should work.
79  */
80 unittest {
81     // use a function from standard library directly.
82     from.std.stdio.writeln("Hallo");
83     // assign something from standard library to a local variable.
84     auto _ = from.std.datetime.stopwatch.AutoStart.yes;
85 }
86 
87 /**
88  * Test that calling a nonexistent function with a string throws with a useful
89  * message.
90  */
91 unittest {
92     import std.algorithm.searching : canFind;
93 
94     auto throws = false;
95     uint containsInfo;
96     try {
97         from.std.stdio.thisFunctionDoesNotExist("Hallo");
98     } catch (Exception ex) {
99         throws = true;
100         containsInfo = canFind(ex.msg, "stdio", "thisFunctionDoesNotExist");
101     }
102 
103     assert(throws);
104     assert(containsInfo == 2);
105 }
106 
107 /**
108  * Test that calling a nonexistent function with a number throws with a useful 
109  * message.
110  */
111 unittest {
112     import std.algorithm.searching : canFind;
113 
114     auto throws = false;
115     uint containsInfo;
116     try {
117         from.std.stdio.thisFunctionDoesNotExist(42);
118         throws = false;
119     } catch (Exception ex) {
120         throws = true;
121         containsInfo = canFind(ex.msg, "stdio", "thisFunctionDoesNotExist");
122     }
123 
124     assert(throws);
125     assert(containsInfo == 2);
126 }