<div dir="ltr">Sorry, it's been a long day. I meant to say Devil. Demons are of course chaotic evil and would not abide by any contract. Devils are lawful evil.</div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Fri, Oct 10, 2025 at 8:29 PM Ethan McCue <<a href="mailto:ethan@mccue.dev">ethan@mccue.dev</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">As with all feature ideas it's worth first asking if it could be implemented by ecosystem tooling instead of being baked into Java.<br><br>For nullable types the answer was yes until the VM started to need to care about nullability for flatness of memory. The only tweak is that when you wrote<br><br><font face="monospace"> void daft(@NonNull String punk) {</font><br><font face="monospace"> IO.println(punk.length());</font><br><font face="monospace"> }</font><br><br><font face="arial, sans-serif">You were not given enforcement by the VM of this invariant. Even though this method was written in a way that assumes it, reflection or other mechanisms that break out of the static checking world could easily pass a null value.<br><br>However there is nothing conceptually preventing the tools validating @NonNull usage from also emitting an error until you have inserted a known precheck.<br><br></font><font face="monospace"> void daft(@NonNull String punk) {<br> // You could get an error until you add this<br></font> <font face="monospace"> Objects.requireNonNull(punk);</font><br><font face="monospace"> IO.println(punk.length());</font><br><font face="monospace"> }<br></font><br>This is not generally done for nullability because most everything tends to be non-null on analysis and that's a <i>lot</i> of <font face="monospace">Objects.requireNonNull</font><font face="arial, sans-serif"> checks</font>.<br><br>But for other single-value invariants, like your @PhoneNumber example, it seems fairly practical. Especially since, as a general rule, arbitrary cost computations really shouldn't be invisible. How would one know if <font face="monospace">(@B A)</font><font face="arial, sans-serif"> is going to thread invocations of some validation method everywhere?<br></font><br><font face="monospace"> void call(@PhoneNumber String me) {<br></font> <font face="monospace"> PhoneNumbers.valdate(me);</font><br><font face="monospace"> dial(me);</font><br><font face="monospace"> }</font><br><br>> How could the language and compiler take the null-restricted types idea and fully generalize it<br><br>This requires a hitherto forbidden incantation to summon the dependent types demon. Upon summoning one must then forge a contract. This contract, if witnessed and notarized by the Sisters of Justice, would be binding. Unfortunately we cannot know the terms of the contract up front, but one must assume it would take a large scale sacrifice. Petitioning nation states might be a good first step, if only to have ready blood enough to spill.<br><br><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Oct 10, 2025 at 7:14 PM Archie Cobbs <<a href="mailto:archie.cobbs@gmail.com" target="_blank">archie.cobbs@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>When I read the draft JEP "Null-Restricted and Nullable Types" a few things really clicked in my head.</div><div><br></div><div>The first is that when reviewing Java code, it is very common to need to convince oneself that some variable != null in order to understand the code or prove its correctness. So the addition of a "!" right in front of its declaration will be a huge, everyday win. I look forward to adding this feature to the growing list of other "How can the compiler help me prove this code is correct?" features like: generics, exhaustive switch cases, sealed types, etc.</div><div><br></div><div>But null-restriction is just one specific example of a more general concept, let's call it "ad hoc type restriction".</div><div><br></div><div>A whole lot of Java code is written using a strategy of "I have some value which can be modeled by type X, but only a subset Y of values of type X are valid for my particular use case, so I'm going to pass these values around as parameters of type X and then either validate them everywhere one can enter from the outside world... which, um, requires me to keep track of which values have come from the outside world and which have not, hmm..."</div><br><div>A null-restricted reference type is just the most common example of this - where the subset Y = X \ { null }.</div><br></div><div dir="ltr"><div>Other examples...</div><div><ul><li>Using int for: The size of some collection
(can't be negative)</li><li>Using byte or char for: An ASCII character</li><li>Using short for: A Unicode basic plane character (2FE0..2FEF are unassigned)</li><li>Using String for: SQL query, phone number, SSN,
etc. (must be non-null and have the proper syntax)</li></ul></div><div><div>But regardless of what X or Y is, it's very common for
the validation step to be missed or forgotten somewhere, leading to
bugs. One might even argue that a <i>majority</i> of bugs are due to incomplete validation of some sort or another. Keeping track of when validation is required and manually adding it in all those places is tedious and error prone.</div></div><div><br></div><div>OK, how could the compiler help me? I am starting with some type T, and I want to derive from it some new type R which only permits a subset of T's values. I want the compiler to guarantee this without too much performance penalty. I want R to be arbitrary - however I define it.</div></div><div dir="ltr"><div><br></div><div>Today I can "homebrew" type restriction on reference types by subclassing or wrapping T, for example:</div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">public class PhoneNumber {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> private String </span><span style="font-family:monospace">e164</span><span style="font-family:monospace">; // non-null and in E.164 format, e.g., "+15105551212"</span></div><div style="margin-left:40px"><span style="font-family:monospace"> public PhoneNumber(String s) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> ... parse, validate E.164 </span><span style="font-family:monospace">syntax</span><span style="font-family:monospace">, normalize, ...</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace"> public String toString() {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> return this.e164;</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div><br></div><div>But doing that is not ideal because:</div><div><ol><li><span style="font-family:arial,sans-serif">This doesn't work for primitive types </span>(e.g., a person's age, grade point average, number of children, etc.)</li><li>A wrapper class loses all of the functionality that comes with the wrapped class. For example, I can't say<span style="font-family:monospace"> pn1.startsWith(pn2) </span><span style="font-family:arial,sans-serif">even though both values are essentially just </span><span style="font-family:monospace">String</span><span style="font-family:arial,sans-serif">s.</span></li><li>There is performance overhead</li></ol></div><div>[Side note - in Valhalla at least problem #3 goes away if the wrapper class is a value class?]</div><div><div><br></div><div>So I pose this question to the group: How could the language and compiler take the null-restricted types idea and fully generalize it so that:</div></div><div><ul><li>It's easy to define custom type restrictions on both primitive and reference types</li><li>Runtime performance is "as good as casting", i.e., validation only occurs when casting</li></ul></div><div>Here's one idea just for concreteness: Use special type restriction annotations, e.g.:</div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">@TypeRestriction(baseType = String.class, enforcer = PhoneNumberEnforcer.class)</span></div><div style="margin-left:40px"><span style="font-family:monospace">public @interface PhoneNumber {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> boolean </span><span style="font-family:monospace">requireNorthAmerican</span><span style="font-family:monospace">() default false;</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">public class PhoneNumberEnforcer implements TypeRestrictionEnforcer<String> {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> <a class="gmail_plusreply" id="m_1409119411655333644m_-2913060356244253013plusReplyChip-2">@Override</a></span></div><div style="margin-left:40px"><span style="font-family:monospace"> public void validate(PhoneNumber restriction, String value) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> if (value == null || !value.matches("\+[1-9][2-9][0-9]{6,14}"))</span></div><div style="margin-left:40px"><span style="font-family:monospace"> throw new </span><span style="font-family:monospace">InvalidPhoneNumberException</span><span style="font-family:monospace">("not in E.164 format");</span></div><div style="margin-left:40px"><span style="font-family:monospace"> if (restriction.</span><span style="font-family:monospace"></span><span style="font-family:monospace">requireNorthAmerican</span><span style="font-family:monospace"></span><span style="font-family:monospace">() && !value.charAt(0) != '1')</span></div><div style="margin-left:40px"><span style="font-family:monospace"> throw new InvalidPhoneNumberException("North American number required");</span></div><div style="margin-left:40px"><span style="font-family:monospace"> }</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">// Some random API...</span></div><div style="margin-left:40px"><span style="font-family:monospace">public void dial(@PhoneNumber String number) { ... }</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">// Some random code...</span></div><div style="margin-left:40px"><span style="font-family:monospace">String input = getUserInput();</span></div><div style="margin-left:40px"><span style="font-family:monospace">try {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> dialer.dial((@PhoneNumber String)input);</span></div><div style="margin-left:40px"><span style="font-family:monospace">} catch (InvalidPhoneNumberException e) {</span></div><div style="margin-left:40px"><span style="font-family:monospace"> System.err.println("Invalid phone number: \"" + input + "\"");</span></div><div style="margin-left:40px"><span style="font-family:monospace">}</span></div><div style="margin-left:40px"><span style="font-family:monospace">String input2 = getUserInput();</span></div><div style="margin-left:40px"><span style="font-family:monospace">dialer.dial(input2); // "warning: implicit cast to </span><span style="font-family:monospace">@PhoneNumber String" ?</span></div><div><br></div><div>The compiler would insert bytecode or method call-outs at the appropriate points to guarantee that any variable with type <span style="font-family:monospace">@PhoneNumber String</span> would always contain a value that has successfully survived <span style="font-family:monospace">PhoneNumberEnforcer.validate()</span>. In other words, it would provide the same level of guarantee as <span style="font-family:monospace">String!</span> does, but it would be checking my custom type constraint instead.</div><div><br></div><div>The Checker Framework does something like the above, but it has limitations due to being a 3rd party tool, it's trying to solve a more general problem, and personally I haven't seen it in widespread use (is it just me?)</div><div><br></div><div>Thoughts?</div><div><br></div><div>-Archie</div><div><br></div><div dir="ltr"><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature">Archie L. Cobbs<br></div></div>
</div>
</div>
</div>
</div>
</blockquote></div>
</blockquote></div>