Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3d6728c
A attempt to create more user-friendly errors for switch exaustiveness.
lahodaj Aug 4, 2025
cad665e
Improvements.
lahodaj Aug 6, 2025
2cfbc71
8364991: Incorrect not-exhaustive error
lahodaj Aug 7, 2025
36e337f
Merge branch 'JDK-8364991' into exhaustiveness-errors
lahodaj Aug 7, 2025
e4f655d
Improving test debuggability.
lahodaj Aug 7, 2025
8647a74
Better name.
lahodaj Aug 7, 2025
2f569e3
Merge branch 'JDK-8364991' into exhaustiveness-errors
lahodaj Aug 7, 2025
5f70d8b
Avoid computing exhaustiveness unnecessarily, cleanup.
lahodaj Aug 12, 2025
74c72e6
Merge branch 'master' into JDK-8364991
lahodaj Sep 11, 2025
8735a3d
Adjusting to spec.
lahodaj Sep 11, 2025
7320a5a
Merge branch 'JDK-8364991' into exhaustiveness-errors
lahodaj Sep 11, 2025
29428b3
Fixing the exhaustiveness search.
lahodaj Sep 11, 2025
bed37c4
Adding test.
lahodaj Sep 12, 2025
0d77887
8367499: Refactor exhaustiveness computation from Flow into a separat…
lahodaj Sep 12, 2025
6963835
Adding doc comment.
lahodaj Sep 12, 2025
d54e53d
Merge branch 'JDK-8367499' into JDK-8367530
lahodaj Sep 12, 2025
31d3990
Cleanup, making timeout work.
lahodaj Sep 12, 2025
ce83803
Enabling disabled test.
lahodaj Sep 25, 2025
0ee6086
Simplifying the code as suggested.
lahodaj Sep 30, 2025
c78f696
Cleanup.
lahodaj Oct 7, 2025
9066c51
Cleanup.
lahodaj Oct 7, 2025
789f09e
Cleanup.
lahodaj Oct 7, 2025
78af109
Fixing tests.
lahodaj Oct 8, 2025
e69c20e
Merge branch 'JDK-8364991' into JDK-8367499
lahodaj Oct 9, 2025
d35773c
Merge branch 'JDK-8367499' into JDK-8367530
lahodaj Oct 9, 2025
38089d1
Better visualisation.
lahodaj Oct 9, 2025
026dc4c
Adding tests with generic records.
lahodaj Oct 13, 2025
9138ef6
Factoring out the 'substitutable' check, as suggested.
lahodaj Oct 15, 2025
51b7fc2
Adding explanation to the replaces map.
lahodaj Oct 15, 2025
11ee4df
Caching isSubtype, as suggested.
lahodaj Oct 15, 2025
25d1b95
Avoiding the use of IdentityHashMap.
lahodaj Oct 17, 2025
998e08c
8367499: Refactor exhaustiveness computation from Flow into a separat…
lahodaj Oct 17, 2025
0290ecb
Merge branch 'JDK-8367499-2' into JDK-8364991
lahodaj Oct 17, 2025
dad7c56
Merge branch 'JDK-8364991-2' into JDK-8367530-2
lahodaj Oct 17, 2025
b79bce1
Fixing tests
lahodaj Oct 20, 2025
ba70f89
Merge branch 'master' into JDK-8364991
lahodaj Oct 21, 2025
e6a3f0d
Merge branch 'master' into JDK-8364991
lahodaj Oct 30, 2025
b7c3334
Merge branch 'JDK-8364991' into JDK-8367530
lahodaj Nov 3, 2025
50ddab0
Reflecting review comments.
lahodaj Nov 7, 2025
75c3cb0
Apply suggestions from code review
lahodaj Nov 13, 2025
8c48cf8
Cleanup, reflecting review feedback.
lahodaj Nov 13, 2025
da13071
Fixing trailing whitespaces.
lahodaj Nov 13, 2025
3e4af72
Merge branch 'master' into JDK-8367530
lahodaj Nov 18, 2025
08fdb6d
Fixing cases that are based on review feedback.
lahodaj Nov 19, 2025
cee029c
Reflecting review feedback: using multi-line diagnostics; passing Pat…
lahodaj Nov 25, 2025
cfa2efa
Merge branch 'master' into JDK-8367530-3
lahodaj Nov 25, 2025
1e6a018
Fixing test.
lahodaj Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public enum SimpleType implements MessageType {
FILE("file", "File", "java.io"),
FILE_OBJECT("file object", "JavaFileObject", "javax.tools"),
PATH("path", "Path", "java.nio.file"),
PATTERN("pattern", "PatternDescription", "com.sun.tools.javac.comp.ExhaustivenessComputer"),
NAME("name", "Name", "com.sun.tools.javac.util"),
LONG("long", "long", null),
NUMBER("number", "int", null),
Expand Down

Large diffs are not rendered by default.

43 changes: 37 additions & 6 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import static com.sun.tools.javac.code.Kinds.Kind.*;
import static com.sun.tools.javac.code.TypeTag.BOOLEAN;
import static com.sun.tools.javac.code.TypeTag.VOID;
import com.sun.tools.javac.comp.ExhaustivenessComputer.ExhaustivenessResult;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
Expand Down Expand Up @@ -696,9 +697,18 @@ public void visitSwitch(JCSwitch tree) {
tree.isExhaustive = tree.hasUnconditionalPattern ||
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases);
if (exhaustiveSwitch) {
tree.isExhaustive |= exhaustiveness.exhausts(tree.selector, tree.cases);
Copy link
Member

@biboudis biboudis Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This |= turned into =. Is that correct? I think so. Now it is guarded by tree.isExhaustive itself right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional. There is one more additional if (!tree.isExhaustive) test below, which replaces the use of the or (but is faster in case we know the switch is exhaustive).

if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustiveStatement);
ExhaustivenessResult exhaustivenessResult = exhaustiveness.exhausts(tree.selector, tree.cases);

tree.isExhaustive = exhaustivenessResult.exhaustive();

if (!tree.isExhaustive) {
if (exhaustivenessResult.notExhaustiveDetails().isEmpty()) {
log.error(tree, Errors.NotExhaustiveStatement);
} else {
logNotExhaustiveError(tree.pos(), exhaustivenessResult, Errors.NotExhaustiveStatementDetails);
}
}
}
}
if (!tree.hasUnconditionalPattern && !exhaustiveSwitch) {
Expand Down Expand Up @@ -735,16 +745,37 @@ public void visitSwitchExpression(JCSwitchExpression tree) {
TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) {
tree.isExhaustive = true;
} else {
tree.isExhaustive = exhaustiveness.exhausts(tree.selector, tree.cases);
}
ExhaustivenessResult exhaustivenessResult = exhaustiveness.exhausts(tree.selector, tree.cases);

tree.isExhaustive = exhaustivenessResult.exhaustive();

if (!tree.isExhaustive) {
log.error(tree, Errors.NotExhaustive);
if (!tree.isExhaustive) {
if (exhaustivenessResult.notExhaustiveDetails().isEmpty()) {
log.error(tree, Errors.NotExhaustive);
} else {
logNotExhaustiveError(tree.pos(), exhaustivenessResult, Errors.NotExhaustiveDetails);
}
}
}

alive = prevAlive;
alive = alive.or(resolveYields(tree, prevPendingExits));
}

private void logNotExhaustiveError(DiagnosticPosition pos,
ExhaustivenessResult exhaustivenessResult,
Error errorKey) {
List<JCDiagnostic> details =
exhaustivenessResult.notExhaustiveDetails()
.stream()
.sorted((pd1, pd2) -> pd1.toString().compareTo(pd2.toString()))
.map(detail -> diags.fragment(Fragments.NotExhaustiveDetail(detail)))
.collect(List.collector());
JCDiagnostic main = diags.error(null, log.currentSource(), pos, errorKey);
JCDiagnostic d = new JCDiagnostic.MultilineDiagnostic(main, details);
log.report(d);
}

public void visitTry(JCTry tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
# number an integer
# option name the name of a command line option
# path a path
# patter a pattern/pattern description
# profile a profile name
# source a source version number, such as 1.5, 1.6, 1.7, taken from a com.sun.tools.javac.code.Source
# source version a source version number, such as 1.5, 1.6, 1.7, taken from a javax.lang.model.SourceVersion
Expand Down Expand Up @@ -1476,6 +1477,18 @@ compiler.err.not.exhaustive=\
compiler.err.not.exhaustive.statement=\
the switch statement does not cover all possible input values

compiler.err.not.exhaustive.details=\
the switch expression does not cover all possible input values\n\
missing patterns:

compiler.err.not.exhaustive.statement.details=\
the switch statement does not cover all possible input values\n\
missing patterns:

# 0: pattern
compiler.misc.not.exhaustive.detail=\
{0}

compiler.err.initializer.must.be.able.to.complete.normally=\
initializer must be able to complete normally

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.tools.JavaFileObject;

Expand All @@ -47,6 +48,9 @@
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.CapturedType;
import com.sun.tools.javac.comp.ExhaustivenessComputer.BindingPattern;
import com.sun.tools.javac.comp.ExhaustivenessComputer.EnumConstantPattern;
import com.sun.tools.javac.comp.ExhaustivenessComputer.RecordPattern;
import com.sun.tools.javac.file.PathFileObject;
import com.sun.tools.javac.jvm.Profile;
import com.sun.tools.javac.jvm.Target;
Expand Down Expand Up @@ -230,6 +234,18 @@ else if (arg instanceof Tag tag) {
return messages.getLocalizedString(l, "compiler.misc.tree.tag." +
StringUtils.toLowerCase(tag.name()));
}
else if (arg instanceof BindingPattern bp) {
return formatArgument(d, bp.type(), l) + " _";
}
else if (arg instanceof RecordPattern rp) {
return formatArgument(d, rp.type(), l) +
Arrays.stream(rp.nested())
.map(pd -> formatArgument(d, pd, l))
.collect(Collectors.joining(", ", "(", ")"));
}
else if (arg instanceof EnumConstantPattern ep) {
return formatArgument(d, ep.type(), l) + "." + ep.enumConstant();
}
else {
return String.valueOf(arg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@
import static com.sun.tools.javac.code.TypeTag.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import com.sun.tools.javac.comp.ExhaustivenessComputer.BindingPattern;
import com.sun.tools.javac.comp.ExhaustivenessComputer.EnumConstantPattern;
import com.sun.tools.javac.comp.ExhaustivenessComputer.RecordPattern;
import static com.sun.tools.javac.util.LayoutCharacters.*;
import static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;
import java.util.Arrays;

/**
* A rich diagnostic formatter is a formatter that provides better integration
Expand Down Expand Up @@ -208,6 +212,7 @@ protected void preprocessDiagnostic(JCDiagnostic diag) {
* @param arg the argument to be translated
*/
protected void preprocessArgument(Object arg) {
//TODO: preprocess for patterns
if (arg instanceof Type type) {
preprocessType(type);
}
Expand All @@ -225,6 +230,17 @@ else if (arg instanceof Iterable<?> iterable && !(arg instanceof Path)) {
preprocessArgument(o);
}
}
else if (arg instanceof BindingPattern bp) {
preprocessArgument(bp.type());
}
else if (arg instanceof RecordPattern rp) {
preprocessArgument(rp.type());
Arrays.stream(rp.nested())
.forEach(this::preprocessArgument);
}
else if (arg instanceof EnumConstantPattern ep) {
preprocessArgument(ep.type());
}
}

/**
Expand Down
10 changes: 7 additions & 3 deletions test/langtools/tools/javac/diags/Example.java
Original file line number Diff line number Diff line change
Expand Up @@ -602,9 +602,13 @@ boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, L
*/
private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
keys.add(d.getCode());
for (Object o: d.getArgs()) {
if (o instanceof JCDiagnostic) {
scanForKeys((JCDiagnostic) o, keys);
List<Object> todoArgs = new ArrayList<>(Arrays.asList(d.getArgs()));
while (!todoArgs.isEmpty()) {
Object o = todoArgs.removeLast();
if (o instanceof JCDiagnostic sd) {
scanForKeys(sd, keys);
} else if (o instanceof List l) {
todoArgs.addAll(l);
}
}
for (JCDiagnostic sd: d.getSubdiagnostics())
Expand Down
3 changes: 2 additions & 1 deletion test/langtools/tools/javac/diags/examples/NotExhaustive.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -22,6 +22,7 @@
*/

// key: compiler.err.not.exhaustive
// options: -XDexhaustivityTimeout=0

class NotExhaustive {
int t(int i) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

// key: compiler.err.not.exhaustive.details
// key: compiler.misc.not.exhaustive.detail
// options: -XDexhaustivityTimeout=-1

class NotExhaustiveDetails {
int t(int i) {
return switch (i) {
case 0 -> -1;
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -22,6 +22,7 @@
*/

// key: compiler.err.not.exhaustive.statement
// options: -XDexhaustivityTimeout=0

class NotExhaustive {
void t(Object o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

// key: compiler.err.not.exhaustive.statement.details
// key: compiler.misc.not.exhaustive.detail
// options: -XDexhaustivityTimeout=-1

class NotExhaustiveDetails {
void t(Object o) {
switch (o) {
case String s -> System.err.println("String of length: " + s.length());
};
}
}
1 change: 1 addition & 0 deletions test/langtools/tools/javac/patterns/Exhaustiveness.java
Original file line number Diff line number Diff line change
Expand Up @@ -2523,6 +2523,7 @@ private void doTest(Path base, String[] libraryCode, String testCode, boolean st
"-Xlint:-preview",
"--class-path", libClasses.toString(),
"-XDshould-stop.at=FLOW",
"-XDexhaustivityTimeout=0",
stopAtFlow ? "-XDshould-stop.ifNoError=FLOW"
: "-XDnoop")
.outdir(classes)
Expand Down
Loading