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 }