From forax at univ-mlv.fr Wed Jun 2 10:42:39 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 2 Jun 2021 12:42:39 +0200 (CEST) Subject: Exhaustiveness mixing sealed type and enum Message-ID: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> Do we agree that the code below defines an exhaustive switch so no default is necessary ? sealed interface List permits Cons, Nil { } record Cons(String value, Object next) implements List { } enum Nil implements List { NIL } int size(List list) { return switch(list) { case Cons cons -> 1 + size(cons.next); case Nil.NIL -> 0 }; } regards, R?mi From forax at univ-mlv.fr Mon Jun 7 09:51:29 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 7 Jun 2021 11:51:29 +0200 (CEST) Subject: case null vs case dominance Message-ID: <524498219.340928.1623059489798.JavaMail.zimbra@u-pem.fr> Hi all, the first part of the message is about javac error message that could be improved, the second part is about the current spec being not very logical. With this code Object o = null; var value = switch(o) { //case null -> 0; case Object __ -> 0; case null -> 0; }; System.out.println(value); The error message is PatternMatching101.java:70: error: this case label is dominated by a preceding case label case null -> 0; ^ The error message is wrong here, because it's 'case null' and you can put a case null where you want but below a total pattern, so the error mesage should reflect that. Here is an example that compiles showing that case null can be below a case String, which it dominates Object o = null; var value = switch(o) { case String s -> 0; case null -> 0; default -> 0; }; Also with default, the spec says that the code below should compile (and it works with javac), because default and case null are disjoint in term of type, but it feels wrong to me. Object o = null; var value = switch(o) { default -> 0; case null -> 0; }; System.out.println(value); The problem is that sometimes 'default' acts as a total pattern sometimes it does not, if there is a case null. I wonder if it's not better to ask users to put the case null on top. R?mi From brian.goetz at oracle.com Mon Jun 7 15:06:20 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 7 Jun 2021 11:06:20 -0400 Subject: case null vs case dominance In-Reply-To: <524498219.340928.1623059489798.JavaMail.zimbra@u-pem.fr> References: <524498219.340928.1623059489798.JavaMail.zimbra@u-pem.fr> Message-ID: <0a29c5a8-14f2-4c15-913a-72f887e14242@oracle.com> On 6/7/2021 5:51 AM, Remi Forax wrote: > Hi all, > the first part of the message is about javac error message that could be improved, > the second part is about the current spec being not very logical. > > With this code > > Object o = null; > var value = switch(o) { > //case null -> 0; > case Object __ -> 0; > case null -> 0; > }; > System.out.println(value); > > The error message is > PatternMatching101.java:70: error: this case label is dominated by a preceding case label > case null -> 0; > ^ > > The error message is wrong here, because it's 'case null' and you can put a case null where you want but below a total pattern, so the error mesage should reflect that. But the case null *is* dominated by the total pattern, and therefore dead. > Here is an example that compiles showing that case null can be below a case String, which it dominates > > Object o = null; > var value = switch(o) { > case String s -> 0; > case null -> 0; > default -> 0; > }; In this case, the String pattern is not total, so it does not dominate null. From forax at univ-mlv.fr Mon Jun 7 15:21:12 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 7 Jun 2021 17:21:12 +0200 (CEST) Subject: case null vs case dominance In-Reply-To: <0a29c5a8-14f2-4c15-913a-72f887e14242@oracle.com> References: <524498219.340928.1623059489798.JavaMail.zimbra@u-pem.fr> <0a29c5a8-14f2-4c15-913a-72f887e14242@oracle.com> Message-ID: <1703672815.671169.1623079272302.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "core-libs-dev" > Cc: "amber-spec-experts" > Envoy?: Lundi 7 Juin 2021 17:06:20 > Objet: Re: case null vs case dominance > On 6/7/2021 5:51 AM, Remi Forax wrote: >> Hi all, >> the first part of the message is about javac error message that could be >> improved, >> the second part is about the current spec being not very logical. >> >> With this code >> >> Object o = null; >> var value = switch(o) { >> //case null -> 0; >> case Object __ -> 0; >> case null -> 0; >> }; >> System.out.println(value); >> >> The error message is >> PatternMatching101.java:70: error: this case label is dominated by a preceding >> case label >> case null -> 0; >> ^ >> >> The error message is wrong here, because it's 'case null' and you can put a case >> null where you want but below a total pattern, so the error mesage should >> reflect that. > > But the case null *is* dominated by the total pattern, and therefore dead. it depends on the order you applies the rules you can also says that because there is a case null, Object does not accept null so it's not a total pattern so it doesn't not dominate null. I think it's an error but i think there should be a special rule for it. > >> Here is an example that compiles showing that case null can be below a case >> String, which it dominates >> >> Object o = null; >> var value = switch(o) { >> case String s -> 0; >> case null -> 0; >> default -> 0; >> }; > > In this case, the String pattern is not total, so it does not dominate null. That's my point, cf above. R?mi From forax at univ-mlv.fr Mon Jun 7 21:38:48 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 7 Jun 2021 23:38:48 +0200 (CEST) Subject: javac throws an AssertionError while compiling a switch on types In-Reply-To: References: <1328401025.342802.1623059678373.JavaMail.zimbra@u-pem.fr> <267de959-1b1b-2f09-c045-85b3d9526829@oracle.com> Message-ID: <1496689718.824645.1623101928396.JavaMail.zimbra@u-pem.fr> promoted to amber-spec-experts because we are talking about the spec more than the implementation. > De: "Brian Goetz" > ?: "jan lahoda" , "Remi Forax" > Cc: "compiler-dev" > Envoy?: Lundi 7 Juin 2021 21:14:40 > Objet: Re: javac throws an AssertionError while compiling a switch on types > The specification (and design) does allow this. > We talked about revising the positioning rules regarding `default`, but in the > end decided not to do anything about this. So putting `default` ahead of > `String s` is allowed. The interpretation of `default` is "anything not handled > by some other case, except null." > In point of fact, what this means is that we have to test all the patterns > before concluding something goes in the default bucket, not just the patterns > that precede the default case. So Strings fall into the `case String s` bucket, > and everything else (except null) falls into the default bucket. This is weird > enough behavior we might consider issuing a warning. I'm not a big fan of warning if it can be an error. I'm old enough to remember when Java was touted as a clean language because it has no warnings (Go is now the new kid on the block on that front). I'm Ok with warnings because of backward compatibility, otherwise, i prefer errors, especially in this case because it helps to convey the semantics. I like the fact that the total pattern has to be the last case. Default is as you say the total pattern minus null, so it seems logical to require it to be the last case too, to convey that the total pattern and default are semantically cousins. As you said, it goes against the fact that you can put default everywhere which is nice in term of code sharing, that's why we have decided to not do anything about it the last time we talk about default. I think i better understand the tradeoff around default that the last time we talk about, so i think we should revisit the spec (for Java 18) to require default to be at the end. But i may have miss a part of the picture ? R?mi > On 6/7/2021 10:17 AM, Jan Lahoda wrote: >> Thanks for the report, Remi. >> I believe the specification currently allows this (default does not have to be >> last, and does not dominate anything), so javac should be able to handle this >> code. I've filled: >> [ https://bugs.openjdk.java.net/browse/JDK-8268333 | >> https://bugs.openjdk.java.net/browse/JDK-8268333 ] >> Jan >> On 07. 06. 21 11:54, Remi Forax wrote: >>> Hi all, >>> javac does like this code, there is a check missing because javac should not >>> reach Gen with a code like this. >>> Object o = null; >>> var value = switch(o) { >>> default -> 0; >>> case String s -> 0; >>> }; >>> System.out.println(value); >>> An exception has occurred in the compiler (17-internal). Please file a bug >>> against the Java compiler via the Java bug reporting page ( [ >>> http://bugreport.java.com/ | http://bugreport.java.com ] ) after checking the >>> Bug Database ( [ http://bugs.java.com/ | http://bugs.java.com ] ) for >>> duplicates. Include your program, the following diagnostic, and the parameters >>> passed to the Java compiler in your report. Thank you. >>> java.lang.AssertionError >>> at jdk.compiler/com.sun.tools.javac.util.Assert.error(Assert.java:155) >>> at jdk.compiler/com.sun.tools.javac.util.Assert.check(Assert.java:46) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.handleSwitch(Gen.java:1310) >>> at >>> jdk.compiler/com.sun.tools.javac.jvm.Gen.doHandleSwitchExpression(Gen.java:1238) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitSwitchExpression(Gen.java:1202) >>> at >>> jdk.compiler/com.sun.tools.javac.tree.JCTree$JCSwitchExpression.accept(JCTree.java:1381) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genExpr(Gen.java:877) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitLetExpr(Gen.java:2376) >>> at jdk.compiler/com.sun.tools.javac.tree.JCTree$LetExpr.accept(JCTree.java:3288) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genExpr(Gen.java:877) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitVarDef(Gen.java:1081) >>> at >>> jdk.compiler/com.sun.tools.javac.tree.JCTree$JCVariableDecl.accept(JCTree.java:1028) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:610) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:645) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:631) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStats(Gen.java:682) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitBlock(Gen.java:1097) >>> at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1092) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:610) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:645) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genMethod(Gen.java:967) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitMethodDef(Gen.java:930) >>> at >>> jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:922) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:610) >>> at jdk.compiler/com.sun.tools.javac.jvm.Gen.genClass(Gen.java:2415) >>> at >>> jdk.compiler/com.sun.tools.javac.main.JavaCompiler.genCode(JavaCompiler.java:737) >>> at >>> jdk.compiler/com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1617) >>> at >>> jdk.compiler/com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1585) >>> at >>> jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:946) >>> at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:317) >>> at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176) >>> at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:64) >>> at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:50) >>> regards, >>> R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Tue Jun 8 21:38:12 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Tue, 8 Jun 2021 21:38:12 +0000 Subject: Exhaustiveness mixing sealed type and enum In-Reply-To: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> References: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> Message-ID: Hi R?mi, On 2 Jun 2021, at 11:42, Remi Forax > wrote: Do we agree that the code below defines an exhaustive switch so no default is necessary ? sealed interface List permits Cons, Nil { } record Cons(String value, Object next) implements List { } enum Nil implements List { NIL } int size(List list) { return switch(list) { case Cons cons -> 1 + size(cons.next); case Nil.NIL -> 0 }; } You are quite right, this should work. I have fixed up the spec to address this. The new definition looks like this: A switch block covers a type T if one of the following is true: * T names an enum class E and all of the enum constants of E appear as constant switch label elements in the switch block. * T supports a sealed class or interface C, and the switch block covers all of the permitted direct subclasses and subinterfaces of C. * A switch label in the switch block has a pattern case label element p where the pattern p is total for T (14.30.3). * There is a default switch label in the switch block. A switch statement or expression is exhaustive if the switch block covers the type of the selector expression. (Neat, huh?) What is this notion of ?supports a sealed class or interface? in the second bulletpoint I hear you ask? It?s actually to address another problem you raised in a different mailing list: sealed interface Vehicle {} record Car(String owner, String color) implements Vehicle {} record Bus(String owner) implements Vehicle {} public static void example2() { var vehicles = List.of( new Car("Bob", "red"), new Bus("Ana") ); for(var vehicle: vehicles) { switch(vehicle) { case Car car -> System.out.println("car !"); case Bus bus -> System.out.println("bus !"); //default -> throw new AssertionError(); } } } PatternMatching101.java:25: error: the switch statement does not cover all possible input values switch(vehicle) { The reason this doesn?t behave as you expected is is that the inferred type for vehicle is not Vehicle but an intersection type! Previously the spec didn?t deal with this, it only asked if the type of the selector expression was a sealed class/interface on the nose. We need to be a little more flexible. So we define the following: A type T supports a sealed class or interface C if and only if one of the following holds: * T is a class type that names C, and the class C is both sealed and abstract. * T is an interface type that names C, and the interface C is sealed. * T is a type variable, and its bound supports C. * T is an intersection type T1 & ... & Tn, and a type Ti supports C (1 ? i ? n). This is what the second bulletpoint for the ?covers? relation uses. This ensures that your vehicle example works as expected as well. The compiler will be updated to match this spec shortly. Thanks for the feedback. Gavin PS: Latest version of the spec available, as always, at: http://cr.openjdk.java.net/~gbierman/jep406/latest -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Wed Jun 9 15:08:17 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 9 Jun 2021 15:08:17 +0000 Subject: Exhaustiveness mixing sealed type and enum In-Reply-To: References: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> Message-ID: <5DBDAD1B-75A3-4601-9907-88D5815D8F91@oracle.com> Ahem, Actually, now I come to think about it - whilst your example enabled me to clean up the notions of coverage and exhaustiveness - I overlooked that your example still will not work because of the way that switch deals with case constants. In the Java 16 spec, it says first: The type of the selector expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (?8.9), or a compile-time error occurs. And then: The switch block of a switch statement or a switch expression is compatible with the type of the selector expression, T, if both of the following are true: * If T is not an enum type, then every case constant associated with the switch block is assignment compatible with T (?5.2). * If T is an enum type, then every case constant associated with the switch block is an enum constant of type T. The important thing to realise is that these rules are driven by the type of the selector expression, not the case constant. Whilst these have been re-jigged in the draft 17 spec, the semantic content has been preserved for compatibility reasons. Back to a simplified version of your example: List l = ... switch(l) { case Nil.NIL -> ... default -> ... } Using the existing rules, this has to fail as List is not an enum type. So, what to do? We could add another rule for checking compatibility of a case label with the selector expression, along the lines of: - If the type of e is not an enum type, char, byte, short, int, Character, Byte, Short, Integer, or String, then e is downcast convertible to the type of c. But that is quite a change to Java. We?d now be able to write things like: Object o = ? switch(o) { case ?Hello? -> ... case Nil.NIL -> ... default -> ... } And code like: switch(o) { case Nil.NIL -> ... } would typecheck only if o is downcast compatible to Nil but NOT if it is actually of type Nil! Confusing? Potentially! But I think actually we need a bigger chat about constants, both on this matter and how constants and patterns should co-exist more generally. I propose we table this for the second preview of this feature. (But the improved definitions of coverage and exhaustiveness stay as I stated them yesterday!) Thanks, Gavin On 8 Jun 2021, at 22:38, Gavin Bierman > wrote: Hi R?mi, On 2 Jun 2021, at 11:42, Remi Forax > wrote: Do we agree that the code below defines an exhaustive switch so no default is necessary ? sealed interface List permits Cons, Nil { } record Cons(String value, Object next) implements List { } enum Nil implements List { NIL } int size(List list) { return switch(list) { case Cons cons -> 1 + size(cons.next); case Nil.NIL -> 0 }; } You are quite right, this should work. I have fixed up the spec to address this. The new definition looks like this: A switch block covers a type T if one of the following is true: * T names an enum class E and all of the enum constants of E appear as constant switch label elements in the switch block. * T supports a sealed class or interface C, and the switch block covers all of the permitted direct subclasses and subinterfaces of C. * A switch label in the switch block has a pattern case label element p where the pattern p is total for T (14.30.3). * There is a default switch label in the switch block. A switch statement or expression is exhaustive if the switch block covers the type of the selector expression. (Neat, huh?) What is this notion of ?supports a sealed class or interface? in the second bulletpoint I hear you ask? It?s actually to address another problem you raised in a different mailing list: sealed interface Vehicle {} record Car(String owner, String color) implements Vehicle {} record Bus(String owner) implements Vehicle {} public static void example2() { var vehicles = List.of( new Car("Bob", "red"), new Bus("Ana") ); for(var vehicle: vehicles) { switch(vehicle) { case Car car -> System.out.println("car !"); case Bus bus -> System.out.println("bus !"); //default -> throw new AssertionError(); } } } PatternMatching101.java:25: error: the switch statement does not cover all possible input values switch(vehicle) { The reason this doesn?t behave as you expected is is that the inferred type for vehicle is not Vehicle but an intersection type! Previously the spec didn?t deal with this, it only asked if the type of the selector expression was a sealed class/interface on the nose. We need to be a little more flexible. So we define the following: A type T supports a sealed class or interface C if and only if one of the following holds: * T is a class type that names C, and the class C is both sealed and abstract. * T is an interface type that names C, and the interface C is sealed. * T is a type variable, and its bound supports C. * T is an intersection type T1 & ... & Tn, and a type Ti supports C (1 ? i ? n). This is what the second bulletpoint for the ?covers? relation uses. This ensures that your vehicle example works as expected as well. The compiler will be updated to match this spec shortly. Thanks for the feedback. Gavin PS: Latest version of the spec available, as always, at: http://cr.openjdk.java.net/~gbierman/jep406/latest -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Wed Jun 9 21:42:16 2021 From: daniel.smith at oracle.com (Dan Smith) Date: Wed, 9 Jun 2021 21:42:16 +0000 Subject: Experience with sealed classes & the "same package" rule Message-ID: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> Here's some late feedback on the sealed classes preview. Probably actionable before 17 GA, if that's what people want to do, or potentially a change that can come in a future release. (Or you can just tell me that this inconvenience isn't a big deal, live with it.) The spec for sealed classes contains this rule: --- If a sealed class C belongs to a named module, then every class named in the permits clause of the declaration of C must belong to the same module as C; otherwise a compile-time error occurs. If a sealed class C belongs to an unnamed module, then every class named in the permits clause of the declaration of C must belong to the same package as C; otherwise a compile-time error occurs. > Sealed class hierarchies are not intended to be declared across different maintenance domains. Modules cannot depend on each other in a circular fashion, yet a sealed class and its direct subclasses need to refer to each other in a circular fashion (in permits and extends clauses, respectively). Necessarily, therefore, a sealed class and its direct subclasses must co-exist in the same module. In an unnamed module, a sealed class and its direct subclasses must belong to the same package. --- A unique property of this rule is that it is guarded on whether code lives in a (named) module or not. If you're in a module, you get language semantics X; if not, you get language semantics Y. In particular, there exist programs that will compile when packaged in a module, but will not when left to the unnamed module. (This is different than the canonical use of modules, which is to restrict the set of packages that are observable, depending on the configuration of the current module.) In practice, I had this experience: 1) Trying to deploy some Java code with a tool that requires a single jar. 2) Need to include the experimental bytecode API, which is packaged as a module. 3) Copy the needed API sources into my own source tree. 4) Compilation fails: lots of "cannot extend a sealed class in a different package" errors. Unfortunately, the bytecode API was making use of sealed classes, and those hierarchies crossed package boundaries. (In a typical public API vs. private implementation packaging strategy.) (The workaround was to declare my own module-info.java and package my program as a single modular jar.) Is this behavior consistent with the design of modules? I lean towards "no": Java sources don't opt in to modules. They belong to a module, or not, depending on the context?whether there's a module-info.java somewhere, which javac parameters are used. The sources themselves are meant to be portable. But this rule means any sealed class hierarchy that crosses package boundaries is asserting *in the program source* that the classes must belong to a module. In practice, I think it's probably a fairly routine experience to take some sources, originally written in a module context, and recompile them in a module-free context. I did it because of idiosyncrasies of my deployment scenario. Others might do it because, say, they're releasing the same API for different audiences. Proposed solution: just drop the whole rule I quoted above. Sealed classes and their children already must be able to "see" each other, so it's impossible to consistently compile the hierarchy in separate maintenance domains. There are ways you could bootstrap your way into it with separate compilation in the unnamed module, but... if you're trying that hard, you probably know what you're doing. It's hard for me to envision *accidentally* creating a sealed hierarchy across maintenance domains. (Or, if we like explicitly stating the same-module rule, even though it's redundant, that would be okay. Just drop the same-package rule in this scenario, and apply the same standard to named and unnamed modules.) From forax at univ-mlv.fr Wed Jun 9 22:20:39 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 10 Jun 2021 00:20:39 +0200 (CEST) Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> Message-ID: <331166841.2528.1623277239681.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "daniel smith" > ?: "amber-spec-experts" > Envoy?: Mercredi 9 Juin 2021 23:42:16 > Objet: Experience with sealed classes & the "same package" rule > Here's some late feedback on the sealed classes preview. Probably actionable > before 17 GA, if that's what people want to do, or potentially a change that > can come in a future release. (Or you can just tell me that this inconvenience > isn't a big deal, live with it.) > > The spec for sealed classes contains this rule: > > --- > > If a sealed class C belongs to a named module, then every class named in the > permits clause of the declaration of C must belong to the same module as C; > otherwise a compile-time error occurs. > > If a sealed class C belongs to an unnamed module, then every class named in the > permits clause of the declaration of C must belong to the same package as C; > otherwise a compile-time error occurs. > >> Sealed class hierarchies are not intended to be declared across different >> maintenance domains. Modules cannot depend on each other in a circular fashion, >> yet a sealed class and its direct subclasses need to refer to each other in a >> circular fashion (in permits and extends clauses, respectively). Necessarily, >> therefore, a sealed class and its direct subclasses must co-exist in the same >> module. In an unnamed module, a sealed class and its direct subclasses must >> belong to the same package. > > --- > > A unique property of this rule is that it is guarded on whether code lives in a > (named) module or not. If you're in a module, you get language semantics X; if > not, you get language semantics Y. In particular, there exist programs that > will compile when packaged in a module, but will not when left to the unnamed > module. (This is different than the canonical use of modules, which is to > restrict the set of packages that are observable, depending on the > configuration of the current module.) > > In practice, I had this experience: > > 1) Trying to deploy some Java code with a tool that requires a single jar. > 2) Need to include the experimental bytecode API, which is packaged as a module. > 3) Copy the needed API sources into my own source tree. > 4) Compilation fails: lots of "cannot extend a sealed class in a different > package" errors. > > Unfortunately, the bytecode API was making use of sealed classes, and those > hierarchies crossed package boundaries. (In a typical public API vs. private > implementation packaging strategy.) > > (The workaround was to declare my own module-info.java and package my program as > a single modular jar.) > > Is this behavior consistent with the design of modules? I lean towards "no": > Java sources don't opt in to modules. They belong to a module, or not, > depending on the context?whether there's a module-info.java somewhere, which > javac parameters are used. The sources themselves are meant to be portable. But > this rule means any sealed class hierarchy that crosses package boundaries is > asserting *in the program source* that the classes must belong to a module. I don't follow you here, a module-info.java is part of the source, it ends with .java after all, so like any .java files if you don't copy them, the behavior of the library will change. As an example independent of sealed types, if there is no module-info, packages are open, if there is a module-info, packages are not open. Thus the behavior of a Java library change depending if there is a module-info or not. So the fact that the behavior of sealed type change if there is a module-info or not, is consistent with the design of modules. Sealed hierarchies are restricted to a package in the unamed module in order to ease the migration when you add a module-info because a sealed hierarchy restricted to a package is obviously restricted to a module. If you relax that rule, you add another hindrance to the adoption of modules. regards, R?mi From john.r.rose at oracle.com Wed Jun 9 22:29:40 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 9 Jun 2021 22:29:40 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> Message-ID: On Jun 9, 2021, at 2:42 PM, Dan Smith > wrote: Here's some late feedback on the sealed classes preview. Probably actionable before 17 GA, if that's what people want to do, or potentially a change that can come in a future release. (Or you can just tell me that this inconvenience isn't a big deal, live with it.) When I read this the first time I thought, ?that?s a sharp edge that will make sealed classes harder to use?. It has the laudable effect of nudging users towards modules, but that?s not (IMO) the job of such a language features. I agree it should not be coupled to modules. It?s up to users to decide what are their maintenance boundaries. It seems very desirable, to me, that adding modularity should subtract permissions only, and not accidentally add some, as in this report. (Also on the same hit-list: The restriction against using local sealed hierarchies. The restriction doesn?t make any sense, logically. And it is a sharp edge when you copy-and-paste a hierarchy as a unit.) ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Wed Jun 9 22:39:20 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 9 Jun 2021 22:39:20 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <331166841.2528.1623277239681.JavaMail.zimbra@u-pem.fr> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <331166841.2528.1623277239681.JavaMail.zimbra@u-pem.fr> Message-ID: <3D69F986-4096-46EA-92BE-6B4BFC8B31E7@oracle.com> On Jun 9, 2021, at 3:20 PM, Remi Forax > wrote: Sealed hierarchies are restricted to a package in the unamed module in order to ease the migration when you add a module-info because a sealed hierarchy restricted to a package is obviously restricted to a module. Adding module-info puts new restrictions on the relations between packages. That?s what modules are for. Subtraction of privilege is a legitimate use of modules. Adding new privileges is not a legitimate use of modules, IMO. If moving to modules is both additive and subtractive, you get two incompatible access control regimes (and even two languages), neither of which is a subset of the other. If you relax that rule, you add another hindrance to the adoption of modules. That?s backwards. The rule, in its current form, is a an incentive to use modules (to get more flexible behavior). But it?s an unnatural incentive: It?s not what language rules are good for. The main ?hindrance? to adding modules is teasing apart cross-package accesses. Having sealed classes be a Very Special Case of cross-package accesses does not help; it only makes the story murkier. So, yes, removing the rule will remove a twisted incentive to go to modules. Is that ?another hindrance? to modules? Maybe, but it?s the right thing here. ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Jun 9 23:16:00 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 10 Jun 2021 01:16:00 +0200 (CEST) Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <3D69F986-4096-46EA-92BE-6B4BFC8B31E7@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <331166841.2528.1623277239681.JavaMail.zimbra@u-pem.fr> <3D69F986-4096-46EA-92BE-6B4BFC8B31E7@oracle.com> Message-ID: <601802793.6456.1623280560558.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Remi Forax" > Cc: "daniel smith" , "amber-spec-experts" > > Envoy?: Jeudi 10 Juin 2021 00:39:20 > Objet: Re: Experience with sealed classes & the "same package" rule > On Jun 9, 2021, at 3:20 PM, Remi Forax < [ mailto:forax at univ-mlv.fr | > forax at univ-mlv.fr ] > wrote: >> Sealed hierarchies are restricted to a package in the unamed module in order to >> ease the migration when you add a module-info because a sealed hierarchy >> restricted to a package is obviously restricted to a module. > Adding module-info puts new restrictions > on the relations between packages. That?s > what modules are for. Subtraction of > privilege is a legitimate use of modules. No, it's more complex than that, adding a module-info just add boundaries. Then depending on the directive, it adds restrictions or it adds privileges. By example, adding a service is adding a privilege access to particular class of a package. > Adding new privileges is not a legitimate > use of modules, IMO. If moving to modules > is both additive and subtractive, you get > two incompatible access control regimes > (and even two languages), neither of which > is a subset of the other. It's both additive and substractive but on different planes (different directives). And even that is not fully true, by default requires is subtractive, if you don't require the content of a module, the packages of that module are not available but because you have incubator modules which content are not available through the unamed modules but available when you require the incubator module. >> If you relax that rule, you add another hindrance to the adoption of modules. > That?s backwards. The rule, in its current > form, is a an incentive to use modules > (to get more flexible behavior). But it?s > an unnatural incentive: It?s not what > language rules are good for. Again, it's about boundaries, if the boundaries do not exist, you can not define the behavior of it. In this particular case, adding a module define the boundaries of the packages that are compiled altogether, so it's safe to have cross package sealed types. Sealed types is a handshake feature, like nestmates, allowing packages from different jars to the part of the same closed hierarchy because there is no module defined is a recipe for disaster. > ? John R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Jun 9 23:27:17 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 10 Jun 2021 01:27:17 +0200 (CEST) Subject: Experience with sealed classes & the "same package" rule In-Reply-To: References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> Message-ID: <631505661.6771.1623281237669.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "daniel smith" > Cc: "amber-spec-experts" > Envoy?: Jeudi 10 Juin 2021 00:29:40 > Objet: Re: Experience with sealed classes & the "same package" rule > (Also on the same hit-list: The restriction against > using local sealed hierarchies. The restriction > doesn?t make any sense, logically. And it is a > sharp edge when you copy-and-paste a hierarchy > as a unit.) yes, local sealed hierarchies should be allowed if they are inside the same scope, it's infuriating to have to move the hierarchy as members of the class when you want to write a unit test with everything confined in the same method. The same also goes for annotations which are not allowed to be local likewise you can not declare them inside a method of a unit test. > ? John R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Jun 10 09:39:54 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 10 Jun 2021 11:39:54 +0200 (CEST) Subject: Exhaustiveness mixing sealed type and enum In-Reply-To: <5DBDAD1B-75A3-4601-9907-88D5815D8F91@oracle.com> References: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> <5DBDAD1B-75A3-4601-9907-88D5815D8F91@oracle.com> Message-ID: <803569425.244163.1623317994125.JavaMail.zimbra@u-pem.fr> > De: "Gavin Bierman" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Mercredi 9 Juin 2021 17:08:17 > Objet: Re: Exhaustiveness mixing sealed type and enum > Ahem, > Actually, now I come to think about it - whilst your example enabled me to clean > up the notions of coverage and exhaustiveness - I overlooked that your example > still will not work because of the way that switch deals with case constants. For whatever reason, in my mind it was Ok to mix constants and type patterns ... sorry for that. > In the Java 16 spec, it says first: >> The type of the selector expression must be char, byte, short, int, Character, >> Byte, Short, Integer, String, or an enum type (?8.9), or a compile-time error >> occurs. > And then: >> The switch block of a switch statement or a switch expression is compatible with >> the type of the selector expression, T, if both of the following are true: > * If T is not an enum type, then every case constant associated with the switch > block is assignment compatible with T (?5.2). > * If T is an enum type, then every case constant associated with the switch > block is an enum constant of type T. > The important thing to realise is that these rules are driven by the type of the > selector expression, not the case constant. Whilst these have been re-jigged in > the draft 17 spec, the semantic content has been preserved for compatibility > reasons. > Back to a simplified version of your example: > List l = ... > switch(l) { > case Nil.NIL -> ... > default -> ... > } > Using the existing rules, this has to fail as List is not an enum type. > So, what to do? We could add another rule for checking compatibility of a case > label with the selector expression, along the lines of: >> - If the type of e is not an enum type, char, byte, short, int, Character, Byte, >> Short, Integer, or String, then e is downcast convertible to the type of c. I don't think you should restrict the type here, i think it should also work for any static final fields by example, so something more like - If e is a constant, then e is downcast convertible to the type of c. > But that is quite a change to Java. We?d now be able to write things like: > Object o = ? > switch(o) { > case ?Hello? -> ... > case Nil.NIL -> ... > default -> ... > } It's not that rare to have code that say, if it's this instance of that instance do that, otherwise if it's that type do this, etc. By example, JSON values are a mix between the constants null, true and false and instances of classes String, Double, JSONArray, JSONObject. > And code like: > switch(o) { > case Nil.NIL -> ... > } > would typecheck only if o is downcast compatible to Nil but NOT if it is > actually of type Nil! Confusing? Potentially! I think you mean the opposite, if o is typed Nil, because NIL is the only value of the enum Nil, the switch is correct while if o is a super type of Nil, then the switch accept more possible instances that just Nil.NIL so the switch does not typecheck without a default or a total pattern. > But I think actually we need a bigger chat about constants, both on this matter > and how constants and patterns should co-exist more generally. I propose we > table this for the second preview of this feature. I agree. > (But the improved definitions of coverage and exhaustiveness stay as I stated > them yesterday!) yes ! > Thanks, > Gavin regards, R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Jun 10 09:48:08 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 10 Jun 2021 11:48:08 +0200 (CEST) Subject: Exhaustiveness mixing sealed type and enum In-Reply-To: References: <11486322.1604454.1622630559619.JavaMail.zimbra@u-pem.fr> Message-ID: <1123013596.251602.1623318488878.JavaMail.zimbra@u-pem.fr> > De: "Gavin Bierman" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Mardi 8 Juin 2021 23:38:12 > Objet: Re: Exhaustiveness mixing sealed type and enum > Hi R?mi, [...] > The new definition looks like this: > A switch block covers a type T if one of the following is true: > * T names an enum class E and all of the enum constants of E appear as constant > switch label elements in the switch block. > * T supports a sealed class or interface C, and the switch block covers all of > the permitted direct subclasses and subinterfaces of C. > * A switch label in the switch block has a pattern case label element p where > the pattern p is total for T (14.30.3). > * There is a default switch label in the switch block. > A switch statement or expression is exhaustive if the switch block covers the > type of the selector expression. (Neat, huh?) > What is this notion of ?supports a sealed class or interface? in the second > bulletpoint I hear you ask? It?s actually to address another problem you raised > in a different mailing list: > sealed interface Vehicle {} > record Car(String owner, String color) implements Vehicle {} > record Bus(String owner) implements Vehicle {} > public static void example2() { > var vehicles = List.of( > new Car("Bob", "red"), > new Bus("Ana") > ); > for(var vehicle: vehicles) { > switch(vehicle) { > case Car car -> System.out.println("car !"); > case Bus bus -> System.out.println("bus !"); > //default -> throw new AssertionError(); > } > } > } > PatternMatching101.java:25: error: the switch statement does not cover all > possible input values > switch(vehicle) { > The reason this doesn?t behave as you expected is is that the inferred type for > vehicle is not Vehicle but an intersection type! Previously the spec didn?t > deal with this, it only asked if the type of the selector expression was a > sealed class/interface on the nose. Ah, intersection types, it's obvious in retrospect. > We need to be a little more flexible. So we define the following: > A type T supports a sealed class or interface C if and only if one of the > following holds: > * T is a class type that names C, and the class C is both sealed and abstract. > * T is an interface type that names C, and the interface C is sealed. > * T is a type variable, and its bound supports C. > * T is an intersection type T1 & ... & Tn, and a type Ti supports C (1 ? i ? n). > This is what the second bulletpoint for the ?covers? relation uses. This ensures > that your vehicle example works as expected as well. So it means that just for the example above, Vehicle does not need to be sealed, which is a kind of mind blowing. > The compiler will be updated to match this spec shortly. > Thanks for the feedback. > Gavin R?mi > PS: Latest version of the spec available, as always, at: [ > http://cr.openjdk.java.net/~gbierman/jep406/latest | > http://cr.openjdk.java.net/~gbierman/jep406/latest ] -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Mon Jun 21 13:10:25 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 21 Jun 2021 14:10:25 +0100 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> Message-ID: <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> Hi, I understand the issue you bring up here. I generally agree that having a rules which depend on whether an application or library is modularized or not, seems unfortunate. But... When working with modules, it is a fairly common practice to structure applications are libraries in multiple set of packages - a package containing the publicly exported types by a given module, and another package containing the "implementation types". I find this way of splitting much more natural than dumping everything into the same package and rely on access modifiers to only expose what I want, and, we use this pattern extensively in the Panama API. So, in this sense, the fact that the "sealed" modifier has special treatment for module is, IMHO, *not* an incentive to use module, as much as a _forced_ move to make sure that sealed remains useful in a modularized application/library. Could we live w/o that rule? Sure we probably could, but let's not assume that dropping that rule is "free": the Panama API/impl would have to be refactored pretty heavily to take advantage of sealing w/o that rule (while, with current rules we could just drop "sealed" where it belongs, and everything just works). To summarize, playing nice w.r.t. modules has a cost (which Dan email summarizes well) and benefits (the ones which I try to capture here). We should decide whether to keep vs. reject the sealed module rule only if we conclude that the cost for supporting that rule outweights the benefits. And, from what I've heard, I'm not sure that's clearly the case now. Maurizio On 09/06/2021 22:42, Dan Smith wrote: > Here's some late feedback on the sealed classes preview. Probably actionable before 17 GA, if that's what people want to do, or potentially a change that can come in a future release. (Or you can just tell me that this inconvenience isn't a big deal, live with it.) > > The spec for sealed classes contains this rule: > > --- > > If a sealed class C belongs to a named module, then every class named in the permits clause of the declaration of C must belong to the same module as C; otherwise a compile-time error occurs. > > If a sealed class C belongs to an unnamed module, then every class named in the permits clause of the declaration of C must belong to the same package as C; otherwise a compile-time error occurs. > >> Sealed class hierarchies are not intended to be declared across different maintenance domains. Modules cannot depend on each other in a circular fashion, yet a sealed class and its direct subclasses need to refer to each other in a circular fashion (in permits and extends clauses, respectively). Necessarily, therefore, a sealed class and its direct subclasses must co-exist in the same module. In an unnamed module, a sealed class and its direct subclasses must belong to the same package. > --- > > A unique property of this rule is that it is guarded on whether code lives in a (named) module or not. If you're in a module, you get language semantics X; if not, you get language semantics Y. In particular, there exist programs that will compile when packaged in a module, but will not when left to the unnamed module. (This is different than the canonical use of modules, which is to restrict the set of packages that are observable, depending on the configuration of the current module.) > > In practice, I had this experience: > > 1) Trying to deploy some Java code with a tool that requires a single jar. > 2) Need to include the experimental bytecode API, which is packaged as a module. > 3) Copy the needed API sources into my own source tree. > 4) Compilation fails: lots of "cannot extend a sealed class in a different package" errors. > > Unfortunately, the bytecode API was making use of sealed classes, and those hierarchies crossed package boundaries. (In a typical public API vs. private implementation packaging strategy.) > > (The workaround was to declare my own module-info.java and package my program as a single modular jar.) > > Is this behavior consistent with the design of modules? I lean towards "no": Java sources don't opt in to modules. They belong to a module, or not, depending on the context?whether there's a module-info.java somewhere, which javac parameters are used. The sources themselves are meant to be portable. But this rule means any sealed class hierarchy that crosses package boundaries is asserting *in the program source* that the classes must belong to a module. > > In practice, I think it's probably a fairly routine experience to take some sources, originally written in a module context, and recompile them in a module-free context. I did it because of idiosyncrasies of my deployment scenario. Others might do it because, say, they're releasing the same API for different audiences. > > Proposed solution: just drop the whole rule I quoted above. Sealed classes and their children already must be able to "see" each other, so it's impossible to consistently compile the hierarchy in separate maintenance domains. There are ways you could bootstrap your way into it with separate compilation in the unnamed module, but... if you're trying that hard, you probably know what you're doing. It's hard for me to envision *accidentally* creating a sealed hierarchy across maintenance domains. > > (Or, if we like explicitly stating the same-module rule, even though it's redundant, that would be okay. Just drop the same-package rule in this scenario, and apply the same standard to named and unnamed modules.) > From brian.goetz at oracle.com Mon Jun 21 13:18:35 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 21 Jun 2021 09:18:35 -0400 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> Message-ID: > When working with modules, it is a fairly common practice to structure > applications are libraries in multiple set of packages - a package > containing the publicly exported types by a given module, and another > package containing the "implementation types". I find this way of > splitting much more natural than dumping everything into the same > package and rely on access modifiers to only expose what I want, and, > we use this pattern extensively in the Panama API. And, there's a reason you use this pattern; the design of the modules feature encourages this.? Earlier explorations of modularity had module-visibility for classes and members; if that were still the case, you could have a public interface and a module-visible implementation of that class in the same package.? But this fine-grained accessibility model was dropped, and modules became groups of packages, some of which are exported and some of which are not.? Unless everything is in one package, it's pretty hard to organize your source base into anything other than "public interfaces in one package, module-public encapsulated implementations in another." From john.r.rose at oracle.com Tue Jun 22 00:31:13 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 22 Jun 2021 00:31:13 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> Message-ID: On Jun 21, 2021, at 6:10 AM, Maurizio Cimadamore > wrote: Could we live w/o that rule? Sure we probably could, but let's not assume that dropping that rule is "free": the Panama API/impl would have to be refactored pretty heavily to take advantage of sealing w/o that rule (while, with current rules we could just drop "sealed" where it belongs, and everything just works). I?d like us to say little more about this forced refactoring, because I?m not fully understanding your point yet. Suppose the rule were dropped, and sealing were allowed across packages in all cases, not just for modularized packages. If you left your code the same, you?d have implementation classes in package P2, as public names, and API classes in package P1, again as public names. The module that contains P1 (perhaps with the same name as P1), exports P1, while it contains but does not export P2. (I agree this is a nice very use of packages that has been enabled by modules!) Code that loads such a non-modularized ?JAR-style? bundle would then have access to public names in P2, since there is no module boundary to prevent that. The effect of stripping modular structure from P1 and P2 is to break open the module so that P2?s contents are more accessible to some random P3. If this breaks the encapsulation pattern of P1 and P2, then it would need to be fixed, if the de-modularized P1 is to be made viable as an alternative product. (Immediate question: Isn?t this just a case of ?if it hurts then don?t do it?? If that?s true, then it doesn?t count as a cost of simplifying the rule, to be agnostic about modularity when validating sealing. It?s just a use case that gets no benefit from simplifying the rule,) I guess fixing this, by re-securing P2 somehow, would require the refactoring you are talking about here, perhaps merging P2 into P1 so P2?s public classes can be downgraded to package-private. There would be other fixes possible also, such as having P2?s types validate their use through capability passing Lookups. That gums up the code but avoids large scale refactors. And the sealing would remain intact. The cross-calls into P2 API points that are private would have to use capabilities. Have I got the gist of it? If so, I don?t think the relative usefulness of modularity (with or without sealing) is anything other than a neutral observation. Modularity is useful, and if you back out of it, you have to give up some utility. Perhaps you have to add other means of access checking (capabilities) to make up the deficit. That argument does not make sealing less useful or more dangerous in a non-modular setting, in a manner unique to sealing. So, I still fail to see why the proposed simplification has any downside at all. ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Jun 22 09:08:23 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 22 Jun 2021 11:08:23 +0200 (CEST) Subject: Experience with sealed classes & the "same package" rule In-Reply-To: References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> Message-ID: <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Maurizio Cimadamore" > Cc: "daniel smith" , "amber-spec-experts" > > Envoy?: Mardi 22 Juin 2021 02:31:13 > Objet: Re: Experience with sealed classes & the "same package" rule > That argument does not make sealing > less useful or more dangerous in a > non-modular setting, in a manner > unique to sealing. So, I still fail to see > why the proposed simplification has > any downside at all. The proposed simplification allows different packages to share different part of the sealed hierarchy without a module. So those packages can be in different jars, compiled at different times. This will produce "impossible" sealed hierarchies where by example two types are both permitted subtypes of each other. We can save a lot of test and debugging time to a lot of people by avoiding split sealed hierarchy. > ? John R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Tue Jun 22 10:05:14 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Tue, 22 Jun 2021 11:05:14 +0100 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> Message-ID: <9e1ee402-f142-a46f-e3ae-5b4e75c1d7cc@oracle.com> On 22/06/2021 10:08, Remi Forax wrote: > > > ------------------------------------------------------------------------ > > *De: *"John Rose" > *?: *"Maurizio Cimadamore" > *Cc: *"daniel smith" , > "amber-spec-experts" > *Envoy?: *Mardi 22 Juin 2021 02:31:13 > *Objet: *Re: Experience with sealed classes & the "same package" rule > > > > That argument does not make sealing > less useful or more dangerous in a > non-modular setting, in a manner > unique to sealing. ?So, I still fail to see > why the proposed simplification has > any downside at all. > > > The proposed simplification allows different packages to share > different part of the sealed hierarchy without a module. > So those packages can be in different jars, compiled at different times. > This will produce "impossible" sealed hierarchies where by example two > types are both permitted subtypes of each other. > > We can save a lot of test and debugging time to a lot of people by > avoiding split sealed hierarchy. I was working under the assumption that we would NOT just blindly allow different packages (e.g. in unnamed module) to access same sealed hierarchy in an uncontrolled fashion. Without the natural boundary provided by modules, it seems like this would be problematic, and would essentially rely on the user "not trying too hard" (as Dan put it). So IMHO, the choice is between keeping the rules we have now, or backout the special rules around modules, and keeping all accesses to a sealed hierarchy package-confined. The middle ground seems murky and not strong enough, from a language perspective. On the other hand, I recall the discussion around meaning of "public" when a class is in a module, and perhaps this is just "more of the same" - e.g. "sealed" provides a stronger guarantee _inside_ a named module. Maurizio > > > ? John > > > R?mi > -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Wed Jun 23 02:35:06 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 23 Jun 2021 02:35:06 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> Message-ID: On Jun 22, 2021, at 2:08 AM, Remi Forax wrote: > > The proposed simplification allows different packages to share different part of the sealed hierarchy without a module. > So those packages can be in different jars, compiled at different times. > This will produce "impossible" sealed hierarchies where by example two types are both permitted subtypes of each other. > > We can save a lot of test and debugging time to a lot of people by avoiding split sealed hierarchy. Nah. The JVM (and probably javac) has to check for broken inputs always, regardless of how likely those broken inputs might be. From forax at univ-mlv.fr Wed Jun 23 09:50:52 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 23 Jun 2021 11:50:52 +0200 (CEST) Subject: Experience with sealed classes & the "same package" rule In-Reply-To: References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> Message-ID: <1615229237.1406136.1624441852341.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "John Rose" > ?: "Remi Forax" > Cc: "Maurizio Cimadamore" , "daniel smith" , > "amber-spec-experts" > Envoy?: Mercredi 23 Juin 2021 04:35:06 > Objet: Re: Experience with sealed classes & the "same package" rule > On Jun 22, 2021, at 2:08 AM, Remi Forax wrote: >> >> The proposed simplification allows different packages to share different part of >> the sealed hierarchy without a module. >> So those packages can be in different jars, compiled at different times. >> This will produce "impossible" sealed hierarchies where by example two types are >> both permitted subtypes of each other. >> >> We can save a lot of test and debugging time to a lot of people by avoiding >> split sealed hierarchy. > > Nah. The JVM (and probably javac) has to check > for broken inputs always, regardless of how > likely those broken inputs might be. The OpenJDK is rich in term of engineering hours, i was thinking more about the other tools that consume the source file and the class file that are not in such position of privilege. R?mi From brian.goetz at oracle.com Wed Jun 23 13:04:35 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 23 Jun 2021 09:04:35 -0400 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> Message-ID: <765f6928-722f-7f42-00b3-d6bc62477849@oracle.com> The rule is the tail; the dog is that "sealing is tight coupling, so sealed hierarchies should be co-maintained."? We don't have formal maintenance boundaries, but packages (for non-modular code) and modules (for modular code) are the closest approximation.? Bristling at the rule is really bristling at the fact that we don't have a formalized notion of maintenance domain. On 6/21/2021 8:31 PM, John Rose wrote: > On Jun 21, 2021, at 6:10 AM, Maurizio Cimadamore > > wrote: >> >> Could we live w/o that rule? Sure we probably could, but let's not >> assume that dropping that rule is "free": the Panama API/impl would >> have to be refactored pretty heavily to take advantage of sealing w/o >> that rule (while, with current rules we could just drop "sealed" >> where it belongs, and everything just works). > > I?d like us to say little more about this forced > refactoring, because I?m not fully understanding > your point yet. > > Suppose the rule were dropped, and sealing > were allowed across packages in all cases, > not just for modularized packages. > > If you left your code the same, you?d have > implementation classes in package P2, > as public names, and API classes in package > P1, again as public names. ?The module that > contains P1 (perhaps with the same name > as P1), exports P1, while it contains but does > not export P2. > > (I agree this is a nice very use of packages > that has been enabled by modules!) > > Code that loads such a non-modularized > ?JAR-style? bundle would then have access > to public names in P2, since there is no > module boundary to prevent that. > > The effect of stripping modular structure > from P1 and P2 is to break open the module > so that P2?s contents are more accessible > to some random P3. > > If this breaks the encapsulation pattern of > P1 and P2, then it would need to be fixed, > if the de-modularized P1 is to be made > viable as an alternative product. > > (Immediate question: ?Isn?t this just a case > of ?if it hurts then don?t do it?? ?If that?s > true, then it doesn?t count as a cost of > simplifying the rule, to be agnostic about > modularity when validating sealing. > It?s just a use case that gets no benefit > from simplifying the rule,) > > I guess fixing this, by re-securing P2 somehow, > would require the refactoring you are > talking about here, perhaps merging P2 > into P1 so P2?s public classes can be > downgraded to package-private. > > There would be other fixes possible also, > such as having P2?s types validate their > use through capability passing Lookups. > That gums up the code but avoids large > scale refactors. ?And the sealing would > remain intact. ?The cross-calls into > P2 API points that are private would > have to use capabilities. > Have I got the gist of it? > > If so, I don?t think the relative usefulness > of modularity (with or without sealing) is > anything other than a neutral observation. > Modularity is useful, and if you back out > of it, you have to give up some utility. > Perhaps you have to add other means of > access checking (capabilities) to make up > the deficit. > > That argument does not make sealing > less useful or more dangerous in a > non-modular setting, in a manner > unique to sealing. ?So, I still fail to see > why the proposed simplification has > any downside at all. > > ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Thu Jun 24 00:25:04 2021 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 24 Jun 2021 00:25:04 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <9e1ee402-f142-a46f-e3ae-5b4e75c1d7cc@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> <9e1ee402-f142-a46f-e3ae-5b4e75c1d7cc@oracle.com> Message-ID: <9173BA2B-CBAF-436D-8D48-CF4BAE806F83@oracle.com> > On Jun 22, 2021, at 4:05 AM, Maurizio Cimadamore wrote: > > I was working under the assumption that we would NOT just blindly allow different packages (e.g. in unnamed module) to access same sealed hierarchy in an uncontrolled fashion. > > Without the natural boundary provided by modules, it seems like this would be problematic, and would essentially rely on the user "not trying too hard" (as Dan put it). So IMHO, the choice is between keeping the rules we have now, or backout the special rules around modules, and keeping all accesses to a sealed hierarchy package-confined. The middle ground seems murky and not strong enough, from a language perspective. Yeah, to clarify: I was *not* suggesting that we should be more strict in module code. Cross-package type hierarchies are a useful feature! (One that we didn't invent when we created modules.) My high-order complaint is that module source code can't be repackaged into an unnamed module, and this is something new in the language. My proposed resolution of this inconsistency is to be *less* strict, allowing cross-package extension in an unnamed module. > On Jun 23, 2021, at 7:04 AM, Brian Goetz wrote: > > The rule is the tail; the dog is that "sealing is tight coupling, so sealed hierarchies should be co-maintained." We don't have formal maintenance boundaries, but packages (for non-modular code) and modules (for modular code) are the closest approximation. Bristling at the rule is really bristling at the fact that we don't have a formalized notion of maintenance domain. I'm not really following the concern here. I understand discouraging "bad" usage of the feature, which is why, all things being equal, sure, why not have a rule where javac says "it looks like you're doing this wrong." But with those sorts of error checks, we want to err on the side of permissiveness?I can't tell for sure whether you're misusing the feature, so I'll let it go. Another category of error check is when there's a mandatory invariant that must be enforced, even if it involves false positives or annoying guardrails. It's not clear to me why this error check would fall into that category. Maybe a specific example could help? Start with: package foo; class A {} Compile to foo.jar. package bar; final class B extends foo.A {} Compile with foo.jar on the classpath, get bar.jar. Now refactor: package foo; sealed class A permits bar.B {} Compile with bar.jar on the classpath. Either: 1) Error, "bar.B is in a different package"; or 2) Generate a revised foo.jar Under (2), I've violated the "single maintenance domain" assumption, but... when I run with foo.jar and bar.jar on the class path, everything works just fine. I've manually introduced an explicit dependency from foo.jar on bar.jar at compile time, which seems like a stupid thing to do, but sealed classes don't seem any more problematic in this regard than mutually recursive method signatures, etc. As John noted, the JVM doesn't care about any of this and enforces its own rules. So: I'm not seeing a mandatory invariant that must be enforced, and that justifies erring on the side of false positives. > On Jun 22, 2021, at 3:08 AM, Remi Forax wrote: > > The proposed simplification allows different packages to share different part of the sealed hierarchy without a module. > So those packages can be in different jars, compiled at different times. > This will produce "impossible" sealed hierarchies where by example two types are both permitted subtypes of each other. Class circularities are prohibited at both compile time and run time. Nothing new here. Extra classes in a PermittedSubclasses attribute are harmless?the attribute only has the effect of rejecting a certain class if it claims to extend another but is not in the permitted list. I'd love to have another achievable scenario of concern to think about (other than the one I outlined above), but I'm having trouble envisioning it. From maurizio.cimadamore at oracle.com Thu Jun 24 00:45:36 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 24 Jun 2021 01:45:36 +0100 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <9173BA2B-CBAF-436D-8D48-CF4BAE806F83@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> <9e1ee402-f142-a46f-e3ae-5b4e75c1d7cc@oracle.com> <9173BA2B-CBAF-436D-8D48-CF4BAE806F83@oracle.com> Message-ID: <6c59a32b-428d-923c-4513-5614611f6954@oracle.com> On 24/06/2021 01:25, Dan Smith wrote: > Under (2), I've violated the "single maintenance domain" assumption, but... when I run with foo.jar and bar.jar on the class path, everything works just fine. I've manually introduced an explicit dependency from foo.jar on bar.jar at compile time, which seems like a stupid thing to do, but sealed classes don't seem any more problematic in this regard than mutually recursive method signatures, etc. > > As John noted, the JVM doesn't care about any of this and enforces its own rules. VM-wise, I tend to agree that this looks far less problematic. Language-wise I was worried about checks for exhaustiveness - but then I was reminded of the fact that a sealed hierarchy has a list of "permits". So it's not like you can keep springing up new implementation into existence - if A permits B and C, well, there are only two classes, namely B and C, _somewhere_ in the classpath, with the usual implications of using classpaths - e.g. that the order in which jars are added to the classpath might affect what "B" and "C" truly are - but I agree, nothing new under the sun here. Where this might run into problems is in cases where the language infers the permits list, which is something that was considered at one point. Of course doing inference in a world where things are not compiled all at the same time starts becoming problematic pretty quickly. Maurizio From chris.hegarty at oracle.com Thu Jun 24 11:08:19 2021 From: chris.hegarty at oracle.com (Chris Hegarty) Date: Thu, 24 Jun 2021 11:08:19 +0000 Subject: Experience with sealed classes & the "same package" rule In-Reply-To: <9173BA2B-CBAF-436D-8D48-CF4BAE806F83@oracle.com> References: <8155E9C9-0A36-4A4E-B2BF-9D904F45C592@oracle.com> <648fcc6e-d440-034a-6675-dda022397f3f@oracle.com> <731868954.795336.1624352903979.JavaMail.zimbra@u-pem.fr> <9e1ee402-f142-a46f-e3ae-5b4e75c1d7cc@oracle.com> <9173BA2B-CBAF-436D-8D48-CF4BAE806F83@oracle.com> Message-ID: > On 24 Jun 2021, at 01:25, Dan Smith wrote: > >> On Jun 22, 2021, at 4:05 AM, Maurizio Cimadamore wrote: >> >> I was working under the assumption that we would NOT just blindly allow different packages (e.g. in unnamed module) to access same sealed hierarchy in an uncontrolled fashion. >> >> Without the natural boundary provided by modules, it seems like this would be problematic, and would essentially rely on the user "not trying too hard" (as Dan put it). So IMHO, the choice is between keeping the rules we have now, or backout the special rules around modules, and keeping all accesses to a sealed hierarchy package-confined. The middle ground seems murky and not strong enough, from a language perspective. > > Yeah, to clarify: I was *not* suggesting that we should be more strict in module code. Cross-package type hierarchies are a useful feature! (One that we didn't invent when we created modules.) > > My high-order complaint is that module source code can't be repackaged into an unnamed module, To be clear, ?re-packaged? here must include a recompilation step. As the rules currently stand, it is possible to ?re-deploy? a module on the class path - since the JVM rules are more lenient than of the JLS rules, here. So, re-deploying is currently possible with the rules as they stand - where ?re-deployment" amounts to moving a modular jar from the module path to the class path. > and this is something new in the language. My proposed resolution of this inconsistency is to be *less* strict, allowing cross-package extension in an unnamed module. This would amount to (substantially) aligning the JLS rules with the current JVM rules, here. Which does not seem unreasonable. In fact, is attractive. Aligning the JVM and JLS rules is attractive from a low-level or OpenJDK developers perspective, but will likely not be all that visible to the majority of developers writing Java code. If we think that, in the absence of explicit modules, the approximation of maintenance domain to package is reasonable, then ok, this rule may carry its own weight. However, it is worth nothing that on the class path there is not a one-to-one relation between packages and jar files - split packages are tolerated. So maybe this JLS rule is not quite living up to its intent? -Chris From forax at univ-mlv.fr Tue Jun 29 13:10:01 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 29 Jun 2021 15:10:01 +0200 (CEST) Subject: TypePattern and "var variable" Message-ID: <773865265.1151695.1624972201587.JavaMail.zimbra@u-pem.fr> I was re-reading JEP 405, 'var foo' is accepted by the grammar as Type Pattern but there is no section explicitly mentioning that. Maybe i'm wrong and 'var foo' is already supported in Java 17, if it's not the case, i think we should add a section about the Type Pattern being enhanced to support 'var'. regards, R?mi From gavin.bierman at oracle.com Wed Jun 30 08:40:16 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 30 Jun 2021 08:40:16 +0000 Subject: TypePattern and "var variable" In-Reply-To: <773865265.1151695.1624972201587.JavaMail.zimbra@u-pem.fr> References: <773865265.1151695.1624972201587.JavaMail.zimbra@u-pem.fr> Message-ID: <483D611A-DD3A-49E1-8719-D95FE305A179@oracle.com> Thanks R?mi. It is discussed in the section ?Array Patterns?. But I can call it out a little more explicitly. Gavin > On 29 Jun 2021, at 14:10, Remi Forax wrote: > > I was re-reading JEP 405, > 'var foo' is accepted by the grammar as Type Pattern but > there is no section explicitly mentioning that. > > Maybe i'm wrong and 'var foo' is already supported in Java 17, > if it's not the case, i think we should add a section about the Type Pattern being enhanced to support 'var'. > > regards, > R?mi