8341176: Permit access to diagnostics for transient snippets

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2024-11-11 09:34:43 +00:00
parent a93bd9dfdd
commit 5ca6698ba4
4 changed files with 91 additions and 7 deletions

View File

@ -176,7 +176,16 @@ class Eval {
List<Snippet> toScratchSnippets(String userSource) {
try {
preserveState = true;
return sourceToSnippets(userSource);
List<Snippet> 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;
}

View File

@ -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.
*
* <p>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<Diag> 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;

View File

@ -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() }.
* <p>

View File

@ -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<String> actual = state.diagnostics(s).map(this::diagToString).toList();
List<String> expected = List.of(expectedDiags);
assertEquals(actual, expected);
}
}