diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java index 0ee6a926b41..bc6f6d30236 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java @@ -176,7 +176,16 @@ class Eval { List toScratchSnippets(String userSource) { try { preserveState = true; - return sourceToSnippets(userSource); + List result = sourceToSnippetsWithWrappers(userSource); + result.forEach(snippet -> { + if (snippet.diagnostics() == null || snippet.diagnostics().isEmpty()) { + //if no better diagnostics set yet, do trial compilation, and + //set diagnostic found: + DiagList fullDiagnostics = state.taskFactory.analyze(snippet.outerWrap(), AnalyzeTask::getDiagnostics); + snippet.setDiagnostics(fullDiagnostics); + } + }); + return result; } finally { preserveState = false; } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java index ffa46cf8be4..4ca8e3830c8 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -678,6 +678,12 @@ public class JShell implements AutoCloseable { * Return the diagnostics of the most recent evaluation of the snippet. * The evaluation can either because of an explicit {@code eval()} call or * an automatic update triggered by a dependency. + * + *

This method will return best-effort diagnostics for snippets returned + * from {@link SourceCodeAnalysis#sourceToSnippets(java.lang.String) }. The + * diagnostics returned for such snippets may differ from diagnostics provided + * after the snippet is {@link #eval(java.lang.String) }-ed. + * * @param snippet the {@code Snippet} to look up * @return the diagnostics corresponding to this snippet. This does not * include unresolvedDependencies references reported in {@code unresolvedDependencies()}. @@ -686,7 +692,7 @@ public class JShell implements AutoCloseable { * this {@code JShell} instance. */ public Stream diagnostics(Snippet snippet) { - return checkValidSnippet(snippet).diagnostics().stream(); + return checkValidSnippet(snippet, true).diagnostics().stream(); } /** @@ -901,10 +907,22 @@ public class JShell implements AutoCloseable { * @return the input Snippet (for chained calls) */ private Snippet checkValidSnippet(Snippet sn) { + return checkValidSnippet(sn, false); + } + + /** + * Check a Snippet parameter coming from the API user + * @param sn the Snippet to check + * @param acceptUnassociated accept snippets that are unassociated + * @throws NullPointerException if Snippet parameter is null + * @throws IllegalArgumentException if Snippet is not from this JShell + * @return the input Snippet (for chained calls) + */ + private Snippet checkValidSnippet(Snippet sn, boolean acceptUnassociated) { if (sn == null) { throw new NullPointerException(messageFormat("jshell.exc.null")); } else { - if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) { + if (sn.key().state() != this || (!acceptUnassociated && sn.id() == Snippet.UNASSOCIATED_ID)) { throw new IllegalArgumentException(messageFormat("jshell.exc.alien", sn.toString())); } return sn; diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 99bfd870f37..0375a2ead65 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -145,7 +145,8 @@ public abstract class SourceCodeAnalysis { * will be {@code "*UNASSOCIATED*"}. * The returned snippets are not associated with the * {@link JShell} instance, so attempts to pass them to {@code JShell} - * methods will throw an {@code IllegalArgumentException}. + * methods will throw an {@code IllegalArgumentException}, unless otherwise + * noted. * They will not appear in queries for snippets -- * for example, {@link JShell#snippets() }. *

diff --git a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java index 3e2e1a839e2..a9b0315e960 100644 --- a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java +++ b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8182270 + * @bug 8182270 8341176 * @summary test non-eval Snippet analysis * @build KullaTesting TestingInputStream * @run testng AnalyzeSnippetTest @@ -32,8 +32,10 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.List; +import java.util.stream.Stream; import jdk.jshell.Snippet; import jdk.jshell.DeclarationSnippet; +import jdk.jshell.Diag; import org.testng.annotations.Test; import jdk.jshell.JShell; @@ -52,6 +54,7 @@ import jdk.jshell.StatementSnippet; import jdk.jshell.TypeDeclSnippet; import jdk.jshell.VarSnippet; import static jdk.jshell.Snippet.SubKind.*; +import jdk.jshell.SourceCodeAnalysis.SnippetWrapper; @Test public class AnalyzeSnippetTest { @@ -141,6 +144,49 @@ public class AnalyzeSnippetTest { SubKind.UNKNOWN_SUBKIND); } + public void testDiagnosticsForSourceSnippet() { + Snippet sn; + sn = assertSnippet("unknown()", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "0-7:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("new String(null, )", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "17-17:compiler.err.illegal.start.of.expr"); + sn = assertSnippet("1 + ", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "3-3:compiler.err.premature.eof"); + sn = assertSnippet("class C {", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "9-9:compiler.err.premature.eof"); + sn = assertSnippet("class C {}", CLASS_SUBKIND); + assertDiagnostics(sn); + sn = assertSnippet("void t() { throw new java.io.IOException(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-43:compiler.err.unreported.exception.need.to.catch.or.throw"); + sn = assertSnippet("void t() { unknown(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-18:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("import unknown.unknown;", SINGLE_TYPE_IMPORT_SUBKIND); + assertDiagnostics(sn, "7-22:compiler.err.doesnt.exist"); + } + + public void testSnippetWrapper() { + SourceCodeAnalysis analysis = state.sourceCodeAnalysis(); + Snippet sn; + String code = "unknown()"; + sn = assertSnippet(code, UNKNOWN_SUBKIND); + SnippetWrapper wrapper = analysis.wrapper(sn); + String wrapped = wrapper.wrapped(); + assertEquals(wrapped, """ + package REPL; + + class $JShell$DOESNOTMATTER { + public static java.lang.Object do_it$() throws java.lang.Throwable { + return unknown(); + } + } + """); + for (int pos = 0; pos < code.length(); pos++) { + int wrappedPos = wrapper.sourceToWrappedPosition(pos); + assertEquals(wrapped.charAt(wrappedPos), code.charAt(pos)); + assertEquals(wrapper.wrappedToSourcePosition(wrappedPos), pos); + } + } + public void testNoStateChange() { assertSnippet("int a = 5;", SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND); assertSnippet("a", SubKind.UNKNOWN_SUBKIND); @@ -159,4 +205,14 @@ public class AnalyzeSnippetTest { assertEquals(sn.subKind(), sk); return sn; } + + private String diagToString(Diag d) { + return d.getStartPosition() + "-" + d.getEndPosition() + ":" + d.getCode(); + } + + private void assertDiagnostics(Snippet s, String... expectedDiags) { + List actual = state.diagnostics(s).map(this::diagToString).toList(); + List expected = List.of(expectedDiags); + assertEquals(actual, expected); + } }