1 module nxt.notnull;
2 
3 /** An of a reference type `T` never being `null`.
4 
5    * Must be initialized when declared.
6 
7    * Can never be assigned the null literal (compile time error).
8 
9    * If assigned a `null` value at runtime an exception will be thrown.
10 
11    `NotNull!T` can be substituted for `T` at any time, but `T` cannot become
12    `NotNull` without some attention: either declaring `NotNull!T`, or using the
13    convenience function, `enforceNotNull!T`.
14 
15    Condition: `T` must be a reference type.
16 
17    Examples:
18    ---
19    int myInt;
20    NotNull!(int *) not_null = &myInt;
21    // you can now use variable not_null anywhere you would
22    // have used a regular int*, but with the assurance that
23    // it never stored null.
24    ---
25 */
26 struct NotNull(T)
27 if (is(T == class) ||
28     is(T == interface) ||
29     is(T == U*, U) && __traits(isScalar, T))
30 {
31     import std.traits: isAssignable;
32 
33     @disable this(); // disallow default initialized (to null)
34 
35     /** Assignment from $(D NotNull) inherited class $(D rhs) to $(D NotNull) base
36         class $(D this). */
37     typeof(this) opAssign(U)(NotNull!U rhs) @safe pure nothrow @nogc
38     if (isAssignable!(T, U))
39     {
40         this._value = rhs._value;
41         return this;
42     }
43 
44     bool opCast(T : bool)() { return _value !is null; }
45 
46     /// Constructs with a runtime not null check (via assert()).
47     this(T value) pure nothrow
48     {
49         assert(value !is null);
50         _value = value;
51     }
52 
53     /** Disable null construction. */
54     @disable this(typeof(null));
55 
56     /** Disable null assignment. */
57     @disable typeof(this) opAssign(typeof(null));
58 
59     @property inout(T) get() inout @safe pure nothrow @nogc
60     {
61         assert(_value !is null);
62         return _value;
63     }
64     alias get this; /// this is substitutable for the regular (nullable) type
65 
66     private T _value;
67 
68     version(none):           // TODO: activate with correct template restriction
69         NotNull!U opCast(U)() pure nothrow
70         if (isAssignable!(U, T))
71         {
72             return NotNull!_value;
73         }
74 
75     version(none)        // NOTE: Disabled because it makes members inaccessible
76     {
77         /* See_Also:
78          * http://forum.dlang.org/thread/aprsozwvnpnchbaswjxd@forum.dlang.org#post-aprsozwvnpnchbaswjxd:40forum.dlang.org
79          */
80         import std.traits: BaseClassesTuple;
81         static if (is(T == class) && !is(T == Object))
82         {
83             @property NotNull!(BaseClassesTuple!T[0]) _valueHelper() inout @trusted pure nothrow
84             {
85                 assert(_value !is null); // sanity check of invariant
86                 return assumeNotNull(cast(BaseClassesTuple!T[0]) _value);
87             }
88         }
89         else
90         {
91             @property inout(T) _valueHelper() inout pure nothrow
92             {
93                 assert(_value !is null); // sanity check of invariant
94                 return _value;
95             }
96         }
97     }
98 
99     // Apparently a compiler bug - the invariant being uncommented breaks all kinds of stuff.
100     // invariant() { assert(_value !is null); }
101 }
102 
103 /** A convenience function to construct a NotNull value from something $(D t)
104     you know isn't null.
105 */
106 NotNull!T assumeNotNull(T)(T t)
107 if (is(T == class) ||
108     is(T == interface) ||
109     is(T == U*, U) && __traits(isScalar, T))
110 {
111     return NotNull!T(t); // note the constructor asserts it is not null
112 }
113 
114 /** A convenience function to check for null $(D t).
115 
116     If you pass null to $(D t), it will throw an exception. Otherwise, return
117     NotNull!T.
118 */
119 NotNull!T enforceNotNull(T, string file = __FILE__, size_t line = __LINE__)(T t)
120 if (is(T == class) ||
121     is(T == interface) ||
122     is(T == U*, U) && __traits(isScalar, T))
123 {
124     import std.exception: enforce;
125     enforce(t !is null, "t is null!", file, line);
126     return NotNull!T(t);
127 }
128 
129 unittest
130 {
131     import core.exception;
132     import std.exception;
133 
134     void NotNullCompilationTest1()() // I'm making these templates to defer compiling them
135     {
136         NotNull!(int*) defaultInitiliation; // should fail because this would be null otherwise
137     }
138     assert(!__traits(compiles, NotNullCompilationTest1!()()));
139 
140     void NotNullCompiliationTest2()()
141     {
142         NotNull!(int*) defaultInitiliation = null; // should fail here too at compile time
143     }
144     assert(!__traits(compiles, NotNullCompiliationTest2!()()));
145 
146     int dummy;
147     NotNull!(int*) foo = &dummy;
148 
149     assert(!__traits(compiles, foo = null)); // again, literal null is caught at compile time
150 
151     int* test;
152 
153     test = &dummy;
154 
155     foo = test.assumeNotNull; // should be fine
156 
157     void bar(int* a) {}
158 
159     // these should both compile, since NotNull!T is a subtype of T
160     bar(test);
161     bar(foo);
162 
163     void takesNotNull(NotNull!(int*) a) { }
164 
165     assert(!__traits(compiles, takesNotNull(test))); // should not work; plain int might be null
166     takesNotNull(foo); // should be fine
167 
168     takesNotNull(test.assumeNotNull); // this should work too
169     assert(!__traits(compiles, takesNotNull(null.assumeNotNull))); // notNull(null) shouldn't compile
170     test = null; // reset our pointer
171 
172     assertThrown!AssertError(takesNotNull(test.assumeNotNull)); // test is null now, so this should throw an assert failure
173 
174     void takesConstNotNull(in NotNull!(int *) a) {}
175 
176     test = &dummy; // make it valid again
177     takesConstNotNull(test.assumeNotNull); // should Just Work
178 
179     NotNull!(int*) foo2 = foo; // we should be able to assign NotNull to other NotNulls too
180     foo2 = foo; // including init and assignment
181 
182 }
183 
184 unittest
185 {
186     class A {}
187     class B : A {}
188     NotNull!B b = (new B).assumeNotNull;
189     NotNull!A a = (new A).assumeNotNull;
190     assert(a && b);
191     a = b;
192     assert(a is b);
193 }
194 
195 unittest
196 {
197     class A {}
198     class B : A {}
199     auto b = assumeNotNull(new B);
200     auto a = assumeNotNull(new A);
201     a = b;
202     assert(a is b);
203 }
204 
205 /** See_Also: http://forum.dlang.org/thread/mxpfzghydhirdtltmmvo@forum.dlang.org?page=3#post-ngtuwqiqumommfrlngjy:40forum.dlang.org */
206 unittest
207 {
208     class A {}
209     class B : A {}
210     void f(NotNull!A a) {}
211     NotNull!B b = assumeNotNull(new B);
212     static assert(!__traits(compiles, { f(b); })); // TODO: I don't want this to fail.
213 }
214 
215 enum isNullInitializable(T) = __traits(compiles, { T _ = null; }); // currently unused
216 
217 ///
218 @safe pure nothrow @nogc unittest
219 {
220     struct S {}
221     struct T { int* _; }
222     static assert( isNullInitializable!(int*));
223     static assert(!isNullInitializable!(int));
224     static assert(isNullInitializable!(string));
225     static assert(!isNullInitializable!(float));
226     static assert(!isNullInitializable!(S));
227     static assert(!isNullInitializable!(T));
228 }