From brian.goetz at oracle.com Mon Nov 4 18:47:25 2024 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 4 Nov 2024 13:47:25 -0500 Subject: Strict field verification In-Reply-To: <8F7B3D31-0209-4AA4-BB21-7AE17D54CF2E@oracle.com> References: <8F7B3D31-0209-4AA4-BB21-7AE17D54CF2E@oracle.com> Message-ID: <096135a8-9dce-484b-bb9b-a814135367d5@oracle.com> This line: > *Each method of a value class that has its|ACC_SYNCHRONIZED|flag set must also have its|ACC_STATIC|flag set.* makes me think we might want to take this further.? The ACC_SYNCHRONIZED bit was a design mistake; it makes for two ways to do the same thing in the bytecode.? We might consider simply not allowing ACC_SYNC in value classes at all, and either (a) have the compiler simulate the effect of a static sync method by wrapping the body with a suitable sync block, or simply (b) disallowing sync methods in value classes entirely. On 10/29/2024 11:59 PM, Dan Smith wrote: > Here's a draft spec for JEP 401 that adds verification rules to ensure that strict instance fields are assigned to before a constructor calls 'super()': > > https://cr.openjdk.org/~dlsmith/jep401/jep401-20241030/specs/value-objects-jvms.html > > Briefly, this change includes: > - Enhancing the treatment of verification flags to support new kinds of flags > - Defining 'flagFieldUnset' flags for unset strict instance fields > - Enhancing StackMapTable to be able to express these flags > - Removing the flags in 'putfield' > - Ensuring the fields are removed in 'invokespecial' > > I'd like to go over it at tomorrow's EG meeting. It's fresh, so I wouldn't be surprised if there are a few bugs! Hopefully it covers all the key new concepts. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Thu Nov 7 20:36:43 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 7 Nov 2024 20:36:43 +0000 Subject: Forward references in initializers Message-ID: I've been looking with Vicente and Ella at the rules for forward references in field/block initializers. I originally specified it in the JEP 401 language spec to maximize expressiveness, but I'm now thinking we'd be better off sticking with the current left-to-right treatment. Here's a test: value class ValueTest { { System.out.println(x); } int x = "abc".length(); } The JEP 401 spec rules say this is allowed, because 'x' has an early initializer, and the block executes late. That is, the method looks like: x = "abc".length(); super(); { System.out.println(x); } This could also manifest with null-restricted types: class NullTest { int x = y.length(); String! y = "abc".substring(1); } Which might compile to: y = "abc".substring(1); super(); x = y.length(); Note that we could actually do something similar with constant fields today, because constant fields are initialized before anything else: class StaticTest { static int x = y.length(); static final String y = "abc"; } But we don't: the rule simply says that forward references are illegal, regardless of the timing of initialization at run time. Reflecting on this, I think all of ValueTest, NullTest, and StaticTest should be errors. I like that, under the existing left-to-right rule, a reader can make sense of the initialization code, *as if* it were all run at once, left to right. In the rare cases that timing/side effects matter, a more sophisticated reader would need to understand that some initializers run early, and others run late. But because of the restrictions on early construction code (no field reads), we can leave the left-to-right reading as "good enough" for most developers, and stick with language rules that reinforce this. From john.r.rose at oracle.com Thu Nov 7 21:27:48 2024 From: john.r.rose at oracle.com (John Rose) Date: Thu, 07 Nov 2024 13:27:48 -0800 Subject: Forward references in initializers In-Reply-To: References: Message-ID: <90568E96-9003-4EE6-9B30-77F1F324A63E@oracle.com> Thank you; I prefer going to simplest possible ordering rules, if I understand this question. (It?s possible I?m missing a point.) I think the simplest rule is the direct conjunction of two independent rules: (a) do not make backwards textual references among initializers, and (b) enforce the new DA rules when those initializers are folded into the constructor. IIUC some ?more expressive? rules can attempt to split the set of non-static fields into two ?buckets?, strict and non-strict, and enforce textual ordering rules only within one ?bucket?. Note also that static fields would also need to be split into two buckets in a parallel way. In fact, the ordering rules already (pre-Valhalla) apply to two buckets: non-static and static. The ?more expressive? rules give us four buckets. I found it hard enough to learn to navigate the two-bucket restrictions, as a young Java programmer. Four buckets would have been a steeper hurdle to get across. One other observation: Field initializers are much more useful for statics than for non-statics, since one field init doesn?t always map cleanly to many non-static field instances, but it does usually map cleanly to a unique static field instance. Thus, adding complexity to gain expressiveness mainly affects statics; the complexity will be there but not beneficial to non-static fields. And even then, the extra expressiveness of being able to write your static (or non-static) fields in more orders (not all orders, just ?more? orders) is not a very big gift to programmers. All Java programmers have learned to sort their fields in response to occasional snarking from javac. Whether there are two or four buckets, they will continue to do so. The four buckets would be perceived as excess fussiness with little upside, I think. HTH On 7 Nov 2024, at 12:36, Dan Smith wrote: > I've been looking with Vicente and Ella at the rules for forward > references in field/block initializers. I originally specified it in > the JEP 401 language spec to maximize expressiveness, but I'm now > thinking we'd be better off sticking with the current left-to-right > treatment. > > Here's a test: > > value class ValueTest { > { System.out.println(x); } > int x = "abc".length(); > } > > The JEP 401 spec rules say this is allowed, because 'x' has an early > initializer, and the block executes late. That is, the method > looks like: > > x = "abc".length(); > super(); > { System.out.println(x); } > > This could also manifest with null-restricted types: > > class NullTest { > int x = y.length(); > String! y = "abc".substring(1); > } > > Which might compile to: > > y = "abc".substring(1); > super(); > x = y.length(); > > Note that we could actually do something similar with constant fields > today, because constant fields are initialized before anything else: > > class StaticTest { > static int x = y.length(); > static final String y = "abc"; > } > > But we don't: the rule simply says that forward references are > illegal, regardless of the timing of initialization at run time. > > Reflecting on this, I think all of ValueTest, NullTest, and StaticTest > should be errors. > > I like that, under the existing left-to-right rule, a reader can make > sense of the initialization code, *as if* it were all run at once, > left to right. In the rare cases that timing/side effects matter, a > more sophisticated reader would need to understand that some > initializers run early, and others run late. But because of the > restrictions on early construction code (no field reads), we can leave > the left-to-right reading as "good enough" for most developers, and > stick with language rules that reinforce this. -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Fri Nov 8 19:59:42 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 8 Nov 2024 19:59:42 +0000 Subject: Forward references in initializers In-Reply-To: References: Message-ID: > On Nov 7, 2024, at 12:36?PM, Dan Smith wrote: > > Reflecting on this, I think all of ValueTest, NullTest, and StaticTest should be errors. > > I like that, under the existing left-to-right rule, a reader can make sense of the initialization code, *as if* it were all run at once, left to right. In the rare cases that timing/side effects matter, a more sophisticated reader would need to understand that some initializers run early, and others run late. But because of the restrictions on early construction code (no field reads), we can leave the left-to-right reading as "good enough" for most developers, and stick with language rules that reinforce this. What does this mean for definite assignment? The current JEP 401 rules model definite assignment according to the actual flow of execution. So something like this would be allowed: value class DATest { final String s1; { System.out.println(s1); } final String s2 = (s1 = "abc"); } But under the left-to-right design principle, we'd say this is an error: it *looks like* s1 is not yet DA in the initializer. It's some weird code that we're rejecting, so no great loss. (Just reorder your initializers.) What if we reverse both the left-to-right order and the run time order? value class DATest2 { final String s1; { s1 = "abc"; } final String s2 = s1; } This seems like a problem that DA needs to catch, but fortunately the reference to s1 is illegal anyway?we're in an early construction context. (You can think of early execution sort of like an optimization, and one that is allowed because we've already proven that this code doesn't depend on the state of the object being initialized.) What about definite unassignment? value class DUTest { final String s1; { s1 = "abc"; } final String s2 = (s1 = "abc"); } The left-to-right analysis will *allow* the assignment to s1 in the block initializer, even though it will have already been assigned by the early initializer of s2. Fortunately, we'll catch the problem when we check the s2 initializer, which is now assigning to a field that is not DU. You could claim that the error is "in the wrong place", but the error occurs nonetheless. Can we rely on this outcome in general? That the set of programs that satisfy the DU rules under a left-to-right interpretation is the same as the set of programs that satisfy the DU rules under an early-then-late interpretation? I *think* so: every field initializer, block initializer, and constructor prologue that contains an assignment to a field is required to consider the field DU beforehand, and non-DU afterwards. However we order those pieces, they always run in a linear sequence, so it's never allowed to do the transition twice. Conclusion: I think I'm happy with a DA/DU analysis that treats initializers as if they run in left-to-right order, before the start of the constructor. It's not really true, but it detects the errors we need to detect with less complexity. From daniel.smith at oracle.com Fri Nov 8 21:49:18 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 8 Nov 2024 21:49:18 +0000 Subject: Forward references in initializers In-Reply-To: References: Message-ID: On Nov 8, 2024, at 11:59?AM, Dan Smith wrote: Conclusion: I think I'm happy with a DA/DU analysis that treats initializers as if they run in left-to-right order, before the start of the constructor. It's not really true, but it detects the errors we need to detect with less complexity. Ugh, never mind, spoke too soon. We have rules that say: - All blank final instance fields must be DU before a 'this()' invocation occurs - All blank strict instance fields must be DA before a 'super()' invocation occurs These rules rely on a more nuanced analysis: the 'this()' rule assumes no initializers have run, while the 'super()' rule assumes early initializers have run. You only get those outcomes by modeling the assignments at the proper place where they will actually occur at run time, and excluding any initializers that won't run until after the invocation. Plus the rules about assignments in the initializers and the prologue need to somehow pass information between each other to prevent multiple assignments. So I think we'll have to stick with the early vs. late DA analysis, even though it works differently than the simpler left-to-right forward reference restriction. And this means DATest should be allowed. -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Sat Nov 9 00:58:55 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Sat, 9 Nov 2024 00:58:55 +0000 Subject: JEP 401 spec updates Message-ID: I've gone through my list of spec feedback and tried to address all outstanding concerns. You can find the latest specs here: https://cr.openjdk.org/~dlsmith/jep401/latest See the change logs for details. The main new content here is specifying StackMapTables and verification rules for guaranteeing strict field initialization (revised a bit from the version I shared last week). For convenience, I also created a redirect for the spec implemented by the EA release, which doesn't include the latest verification changes: https://cr.openjdk.org/~dlsmith/jep401/ea From john.r.rose at oracle.com Sat Nov 9 22:59:23 2024 From: john.r.rose at oracle.com (John Rose) Date: Sat, 09 Nov 2024 14:59:23 -0800 Subject: Forward references in initializers In-Reply-To: References: Message-ID: <6263B431-76C4-4E45-942F-482B2145897D@oracle.com> On 8 Nov 2024, at 13:49, Dan Smith wrote: > On Nov 8, 2024, at 11:59?AM, Dan Smith wrote: > > Conclusion: I think I'm happy with a DA/DU analysis that treats initializers as if they run in left-to-right order, before the start of the constructor. It's not really true, but it detects the errors we need to detect with less complexity. > > Ugh, never mind, spoke too soon. > > We have rules that say: > - All blank final instance fields must be DU before a 'this()' invocation occurs > - All blank strict instance fields must be DA before a 'super()' invocation occurs > > These rules rely on a more nuanced analysis: the 'this()' rule assumes no initializers have run, while the 'super()' rule assumes early initializers have run. You only get those outcomes by modeling the assignments at the proper place where they will actually occur at run time, and excluding any initializers that won't run until after the invocation. > > Plus the rules about assignments in the initializers and the prologue need to somehow pass information between each other to prevent multiple assignments. > > So I think we'll have to stick with the early vs. late DA analysis, even though it works differently than the simpler left-to-right forward reference restriction. And this means DATest should be allowed. Yes. It?s ugly, though. (CLARIFICATION: The fields in any value class can be redundantly marked final, as you have here, BUT ALSO they should be viewed as marked strict, even if we don?t have a syntax for that. I?m going to pretend we have a __Strict keyword.) Here?s what you wrote, with __Strict added: value class DATest { __Strict final String s1; { System.out.println(s1); } __Strict final String s2 = (s1 = "abc"); } It?s legal because the s2-init in pre-super, and so precedes the instance-init, which is post-super. Here is how that class SHOULD be written, to keep it from being so puzzling to read: value class DATest_b { __Strict final String s1; __Strict final String s2 = (s1 = "abc"); { System.out.println(s1); } } Let?s just pretend for a second that we could annotate the instance-init with __Strict as well, meaning the code gets moved to pre-super. Then we could try moving the instance-init to pre-super: value class DATest_c { __Strict final String s1; __Strict { System.out.println(s1); } __Strict final String s2 = (s1 = "abc"); } Gross. And also useless. As I think you observed, s1 cannot be read in a pre-super context. I am happy to acknowledge that there is no call for marking an instance-init with __Strict or any other marker that would change its ordering. DATest_c is just wrong, and the original DATest is a puzzler because it reads like it could be DATest_c. OK, so given all that, I see that it would be nice to employ the left-to-right rule, somehow, to encourage (or even force) programmers to write DATest_b to avoid appearances of disorder. I have an idea here, which I think might pan out: Amend the left-to-right order rule (for non-statics only) to allow only the nicely readable DATest_b. We keep unchanged the existing constraints from Java 1.1, of DA/DU conditions, and left-to-right name def-to-use order, among non-statics. (Same story independently for statics, of course.) THEN, for non-statics only (because there is a distinction between pre-super and post-super inits), we require that all pre-super inits are to the left of all post-super inits. We have five cases of declarations which interact with init-order (either as use or def of fields). A. a strict field with an initializer (pre-super, has-init) B. a strict field with no initializer (pre-super, no-init) C. an instance initialization block (post-super, has-init) D. a non-strict field with an initializer (post-super, has-init) E. a non-strict field with no initializer (post-super?, no-init) The idea I?m trying out here is that any of the five cases that executes init code (A, C, or D) must have an extra left-to-right constraint that places it either with all of the pre-super codes (all A cases together) or all of the post-super codes (all C and D cases together). It boils down to a constraint that all strict initializers (case A) must precede (in left-to-right order) all non-strict initializers (cases C and D). There would be no extra constraint on the no-init cases (B, E), but the existing rule (from Java 1) would keep them before any references. Is case E uniquely strange? Because, different constructors might initialize E either before or after the super. non-value class TwoCons { __NonStrict int caseE; TwoCons(int x) { caseE = x; super(); } TwoCons() { super(); caseE = 42; } } I don?t think any of the other cases can have this ambiguity. I also think it would be harmless to let case E go anywhere in the left-to-right order. He doesn?t have an init, and he can?t be read during the pre-super phase anyway. The same reasoning applies to B. Both B and E fields still must be declared in the left-to-right order before any init that uses the B or E field, per the old (Java 1) rule. Boiling it down: Classify init actions (field initializers or instance initializers) as pre-super or post-super; only strict field initializers are pre-super. Then, require that a post-super init action must never precede (in left to right order) a pre-super init action (i.e., a strict field init). That would remove one kind of paradoxical appearance of disorder. Is it worth it? Maybe. ? John From maurizio.cimadamore at oracle.com Mon Nov 11 12:05:54 2024 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 11 Nov 2024 12:05:54 +0000 Subject: Forward references in initializers In-Reply-To: References: Message-ID: I agree with this conclusion. The main point here is that whether strict fields are initialized before/after a super call is a very low-level detail that we'd like most developers to happily ignore. But if the distinction surfaces up at the level of DA/DU and field assignment, this is no longer strictly true, and it is possible that some developers might be puzzled as a result, and have to dig much deeper than they'd comfortable with to find exactly why that is the case. Preserving the illusion that all fields are created equal seems kind of nice, even though it is still an illusion. Maurizio On 08/11/2024 19:59, Dan Smith wrote: > Conclusion: I think I'm happy with a DA/DU analysis that treats > initializers as if they run in left-to-right order, before the start of > the constructor. It's not really true, but it detects the errors we need > to detect with less complexity. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Nov 11 12:54:12 2024 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Nov 2024 12:54:12 +0000 Subject: Forward references in initializers In-Reply-To: References: Message-ID: <0373AF59-E7A3-4C29-B380-F025003D2BA2@oracle.com> I will reiterate my point that I think changing these rules is something that is more amenable to a strict class, then to a subset of the fields being strict. I continue to think that we are missing an abstraction here. Sent from my iPad On Nov 11, 2024, at 7:06?AM, Maurizio Cimadamore wrote: ? I agree with this conclusion. The main point here is that whether strict fields are initialized before/after a super call is a very low-level detail that we'd like most developers to happily ignore. But if the distinction surfaces up at the level of DA/DU and field assignment, this is no longer strictly true, and it is possible that some developers might be puzzled as a result, and have to dig much deeper than they'd comfortable with to find exactly why that is the case. Preserving the illusion that all fields are created equal seems kind of nice, even though it is still an illusion. Maurizio On 08/11/2024 19:59, Dan Smith wrote: Conclusion: I think I'm happy with a DA/DU analysis that treats initializers as if they run in left-to-right order, before the start of the constructor. It's not really true, but it detects the errors we need to detect with less complexity. -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Mon Nov 11 14:02:07 2024 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 11 Nov 2024 14:02:07 +0000 Subject: Forward references in initializers In-Reply-To: <0373AF59-E7A3-4C29-B380-F025003D2BA2@oracle.com> References: <0373AF59-E7A3-4C29-B380-F025003D2BA2@oracle.com> Message-ID: Having a class-wide feature would certainly help. If a class is strict, I can imagine us explaing to developers how their long-held assumption of when are things ?really initialized? needs some tweaks. But if a class allows strict and non-strict fields to co-exist, I can imagine a lot of confusion to arise as a result. E.g. |class Test { strict int x = 1; int y = x; // ok int z = 2; strict w = z; // error } | Field initialization order is already fairly complex as it is (I mean remembering where a field initializer is effectively expanded into by the compiler). Adding new rules in this area is going to increase complexity. But if the new rules are confined to ?a new kind of class? (as Brian suggests) then it is (subjectively) more manageable. Maurizio On 11/11/2024 12:54, Brian Goetz wrote: > I will reiterate my point that I think changing these rules is > something that is more amenable to a strict class, then to a subset of > the fields being strict. I continue to think that we are missing an > abstraction here. > > Sent from my iPad > >> On Nov 11, 2024, at 7:06?AM, Maurizio Cimadamore >> wrote: >> >> ? >> >> I agree with this conclusion. >> >> The main point here is that whether strict fields are initialized >> before/after a super call is a very low-level detail that we'd like >> most developers to happily ignore. But if the distinction surfaces up >> at the level of DA/DU and field assignment, this is no longer >> strictly true, and it is possible that some developers might be >> puzzled as a result, and have to dig much deeper than they'd >> comfortable with to find exactly why that is the case. Preserving the >> illusion that all fields are created equal seems kind of nice, even >> though it is still an illusion. >> >> Maurizio >> >> >> On 08/11/2024 19:59, Dan Smith wrote: >>> Conclusion: I think I'm happy with a DA/DU analysis that treats >>> initializers as if they run in left-to-right order, before the start of >>> the constructor. It's not really true, but it detects the errors we need >>> to detect with less complexity. ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Wed Nov 13 05:41:49 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 13 Nov 2024 05:41:49 +0000 Subject: Forward references in initializers In-Reply-To: <6263B431-76C4-4E45-942F-482B2145897D@oracle.com> References: <6263B431-76C4-4E45-942F-482B2145897D@oracle.com> Message-ID: > On Nov 9, 2024, at 2:59?PM, John Rose wrote: > > Boiling it down: Classify init actions (field initializers > or instance initializers) as pre-super or post-super; only > strict field initializers are pre-super. Then, require that > a post-super init action must never precede (in left to right > order) a pre-super init action (i.e., a strict field init). > > That would remove one kind of paradoxical appearance of > disorder. Is it worth it? Maybe. Eh, I'm open to it, but it seems kind of counter-productive?sure, a class that follows this rule avoids the tiny risk of surprising sequencing behavior, possibly improving readability (if they happen to do some other odd things); but it does it by forcing all authors to think/care about the sequencing in order to satisfy the rules. I'd rather most programmers just not think about it, and in the rare case that a problem occurs, the compiler can guide them. > On Nov 11, 2024, at 4:05?AM, Maurizio Cimadamore wrote: > > The main point here is that whether strict fields are initialized before/after a super call is a very low-level detail that we'd like most developers to happily ignore. But if the distinction surfaces up at the level of DA/DU and field assignment, this is no longer strictly true, and it is possible that some developers might be puzzled as a result, and have to dig much deeper than they'd comfortable with to find exactly why that is the case. Preserving the illusion that all fields are created equal seems kind of nice, even though it is still an illusion. Ok, but see my next message. :-) In order to use DA/DU to enforce requirements about initialization before 'this()'/'super()' calls, we need DA/DU to model the actual state of the variables at that point, not a simplified version that pretends all initializers run at once. Where that leaves me is: even though it's a little surprising, the DA/DU anomaly I raised is not the end of the world, and the cure seems worse than the disease. Left-to-right is a reasonable approximation of what happens most of the time, but DA/DU needs to be a little more nuanced. Note that for this anomaly to come up, we need two very cornery feature usages: (1) an instance initializer block (or, perhaps, some feature that introduces mixed early/late field initializers); (2) an assignment to a field within another field's initializer. Combine these two little-used features, and you get an initially-surprising but logical result. If you don't like it, don't write your code that way. > On Nov 11, 2024, at 4:54?AM, Brian Goetz wrote: > > I will reiterate my point that I think changing these rules is something that is more amenable to a strict class, then to a subset of the fields being strict. I continue to think that we are missing an abstraction here. The source of complexity that you'd like to eliminate is this: some classes can have both early and late initializer code. In value classes, that manifests as an (early) field initializer plus a (late) initializer block. In uses of '!', that could manifest as mixed '!' and non-'!' fields (depending on how we handle this situation). Given a strictly-initialized-by-default class feature, it could also come up if there were a "but treat this one as late" opt-out. To get to the happier space you're envisioning, we'd need two things: - If any field initializers run early, all field initializers run early. With an appropriate class-level opt-in, this doesn't seem so bad. Corner-case must-be-late code can always be put in a constructor. I'm not sure where it leaves '!' types though. - If there's any early initialization, initializer blocks also run early. But what's the point of an initializer block that can't touch 'this'? Maybe nothing, and we outlaw them in value/strict/whatever classes. Or maybe we support field reads during early construction (as we've discussed elsewhere), and I bet that would address most of the use cases for an initializer block. I'd like to explore this further in some corpus code, but I kind of think getting past the field read limitation would go a long way to making strict-by-default everywhere viable (with, somehow, a minimal opt in for compatibility, giving authors a chance to specially handle things like cycles). > On Nov 11, 2024, at 6:02?AM, Maurizio Cimadamore wrote: > > class Test { > strict int x = 1; > int y = x; // ok > int z = 2; > strict w = z; // error > } > > Field initialization order is already fairly complex as it is (I mean remembering where a field initializer is effectively expanded into by the compiler). Adding new rules in this area is going to increase complexity. Under the "status quo" rules that I'm defending, the error message here is straightforward: you can't reference 'z' in an early construction context. No need to worry about DA/DU. That's the magic that lets us treat early execution almost as an optimization: your code doesn't have any dependency on 'this', so what difference does execution timing make to you, relative to the state of 'this'? ("Almost an optimization", because there's always the tiny risk that the initializer code depends on some external state for which sequencing is important.) I'll note, though, that if we allow field reads in an early construction context, this whole story changes and we'll definitely need to guarantee left-to-right execution. From daniel.smith at oracle.com Wed Nov 13 16:13:50 2024 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 13 Nov 2024 16:13:50 +0000 Subject: EG meeting, 2024-11-13 Message-ID: EG meeting Wednesday, November 13. 17:00 UTC (9am PST, 12pm EST, 6pm CET). Topics: - JEP 401 spec updates - Forward references in initializers From brian.goetz at oracle.com Sat Nov 23 23:42:59 2024 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 23 Nov 2024 18:42:59 -0500 Subject: Fwd: Null-Restricted and Nullable Types In-Reply-To: References: Message-ID: <1b227487-9b16-43ce-8f91-895ea9967e51@oracle.com> Received on valhalla-spec-comments. Notes from the legislative analyst: The author observes that nullability by default is a bad default, and that we should retroactively reinterpret unadorned type names as if they were null-restricted. The author also suggests changing some long-standing policies, such as allowing the `--target` version to be less than the `--source` version. While the author purports this to be a mere "better syntax", this is actually an incompatible, retroactive semantic change. The author is compelled by the notion that "non-nullable would have been a better default", which is unquestionably true (and borne out by empirical efforts of annotating existing libraries.) However, retroactively changing what "String" means would, quite literally, be the biggest incompatible change in Java's history. The issue that nullability by default was a bad choice, and the desire to find an acceptable way to migrate away from this choice, is fully understood.? But we will not be discussing it for the time being, because we simply have way too much work that isn't literally "the biggest incompatible change every suggested for Java" to add this to our plate at this time. -------- Forwarded Message -------- Subject: Null-Restricted and Nullable Types Date: Sat, 23 Nov 2024 21:03:32 +0100 From: Enno Thieleke To: valhalla-spec-comments at openjdk.org Hello, after seeing Brian's very good talk about project Valhalla he held at the JVM Language Summit two months ago, I'd like to suggest a /slightly/ different syntax for null-restricted types. The motivation for it is simple: We'll be using that syntax for 20+ years, so it should be "nice". While this is a highly subjective thing, I think we can agree that having to write (and read) less is preferable to more - even when it comes to single characters. I think the "bang" for null-restricted types is not necessary. While I can't provide any numbers, I'm making the (bold) claim that the majority of variable declarations across all Java source code on the planet is in fact /meant to be/ null-restricted. So instead of writing `String!` for "this mustn't be null" we should really be writing `String`. And when it comes to nullables, we should be writing `String?` as suggested in Brian's talk and just like in other languages. The obvious question is: How can we make existing Java code compatible when the same code suddenly changes semantically if we update to a new Java version? I think this is where the `-source` and `-target` flags of the compiler come into play. Let's assume Java 30 considers `String` to be null-restricted and Java 29 doesn't. Let's take a look at how the flags would affect what: * `-source 30 -target 30` `String` becomes null-restricted, `String?` becomes its nullable pendant, bytecode would be generated with null checks and further information on nullable where required. * `-source 30 -target 29` `String` becomes at least syntactically null-restricted (compile time errors would occur within the same compiler run if used with `null` or a nullable pendant), `String?`?becomes its nullable pendant, and bytecode /could/ be generated with backward compatible null checks, but no information on nullable. * `-source 29 -target 29` is as if Java 29 is used. * `-source 29 -target 30` `String` remains nullable (just like above) and bytecode would be generated with information on nullable pretty much everywhere, because nothing can be null-restricted. But what if we compile against a library? The compiler can figure out, by using the class file version, if parameter, return and field types are null-restricted or not. If the class file version is for Java 29, all types are effectively nullable. If the class file version is for Java 30, null-restrictedness is implied (no information needs to be present, in fact it needn't exist at all) and nullable types are specifically marked as part of the compilation process of a class file. This way it would be possible to use the new null-restricted types by default, but developers can opt-out, which would make sense for a lot of Java codebases out there in the beginning. However, it might also be a good idea to have a special opt-in (or out) compiler flag, instead of the `-source` flag, for making null-restricted types the default or not, in which case the class file version can't be used to determine null-restrictedness and I couldn't come up with a different solution than introducing a flag in the byte code. With a special compiler flag it would be possible to use new Java 31 and 32, etc. `-source` levels, without ever activating the null-restricted types by default, which would make the transition to null-restricted types easier, because it wouldn't be "enforced". In essence Java would give developers a choice: Stick to "legacy" nullable types or enable null-restricted types for the entire code of a compile process at once. This may or may not be a naive approach to dealing with the situation, I'm not sure, but I'd like to emphasize that your decision will set the new syntax in stone for the foreseeable future and should therefore prioritize good code readability, as it is crucial and in my opinion one of the key features of the Java programming language and I hope that it still will be in the years to come. Thank you very much and regards Enno -------------- next part -------------- An HTML attachment was scrubbed... URL: