8235474: JShell does not handle records properly

Reviewed-by: vromero, rfield
This commit is contained in:
Jan Lahoda 2019-12-11 13:08:42 +01:00
parent a20fa5b995
commit 308aea3e0b
9 changed files with 136 additions and 16 deletions

View file

@ -140,7 +140,8 @@ module java.base {
exports jdk.internal to exports jdk.internal to
java.compiler, java.compiler,
jdk.jfr, jdk.jfr,
jdk.compiler; jdk.compiler,
jdk.jshell;
exports jdk.internal.access to exports jdk.internal.access to
java.desktop, java.desktop,
java.logging, java.logging,

View file

@ -1238,6 +1238,7 @@ public class Check {
if ((flags & RECORD) != 0) { if ((flags & RECORD) != 0) {
// records can't be declared abstract // records can't be declared abstract
mask &= ~ABSTRACT; mask &= ~ABSTRACT;
implicit |= FINAL;
} }
// Imply STRICTFP if owner has STRICTFP set. // Imply STRICTFP if owner has STRICTFP set.
implicit |= sym.owner.flags_field & STRICTFP; implicit |= sym.owner.flags_field & STRICTFP;

View file

@ -3722,7 +3722,7 @@ public class JavacParser implements Parser {
protected JCClassDecl recordDeclaration(JCModifiers mods, Comment dc) { protected JCClassDecl recordDeclaration(JCModifiers mods, Comment dc) {
int pos = token.pos; int pos = token.pos;
nextToken(); nextToken();
mods.flags |= Flags.RECORD | Flags.FINAL; mods.flags |= Flags.RECORD;
Name name = typeName(); Name name = typeName();
List<JCTypeParameter> typarams = typeParametersOpt(); List<JCTypeParameter> typarams = typeParametersOpt();
@ -4117,9 +4117,10 @@ public class JavacParser implements Parser {
} }
} }
boolean isRecordStart() { protected boolean isRecordStart() {
if (token.kind == IDENTIFIER && token.name() == names.record && if (token.kind == IDENTIFIER && token.name() == names.record &&
(peekToken(TokenKind.IDENTIFIER, TokenKind.LPAREN) || (peekToken(TokenKind.IDENTIFIER, TokenKind.LPAREN) ||
peekToken(TokenKind.IDENTIFIER, TokenKind.EOF) ||
peekToken(TokenKind.IDENTIFIER, TokenKind.LT))) { peekToken(TokenKind.IDENTIFIER, TokenKind.LT))) {
checkSourceLevel(Feature.RECORDS); checkSourceLevel(Feature.RECORDS);
return true; return true;

View file

@ -670,12 +670,12 @@ class CompletenessAnalyzer {
public Completeness parseDeclaration() { public Completeness parseDeclaration() {
boolean isImport = token.kind == IMPORT; boolean isImport = token.kind == IMPORT;
boolean isDatum = false; boolean isRecord = false;
boolean afterModifiers = false; boolean afterModifiers = false;
boolean isBracesNeeded = false; boolean isBracesNeeded = false;
while (token.kind.isDeclaration()) { while (token.kind.isDeclaration()) {
isBracesNeeded |= token.kind.isBracesNeeded(); isBracesNeeded |= token.kind.isBracesNeeded();
isDatum |= !afterModifiers && token.kind == TK.IDENTIFIER && token.tok.name() == names.record; isRecord |= !afterModifiers && token.kind == TK.IDENTIFIER && token.tok.name() == names.record;
afterModifiers |= !token.kind.isModifier(); afterModifiers |= !token.kind.isModifier();
nextToken(); nextToken();
} }
@ -696,17 +696,11 @@ class CompletenessAnalyzer {
case SEMI: case SEMI:
return Completeness.COMPLETE; return Completeness.COMPLETE;
case IDENTIFIER: case IDENTIFIER:
return isBracesNeeded return isBracesNeeded || isRecord
? Completeness.DEFINITELY_INCOMPLETE ? Completeness.DEFINITELY_INCOMPLETE
: Completeness.COMPLETE_WITH_SEMI; : Completeness.COMPLETE_WITH_SEMI;
case BRACKETS: case BRACKETS:
return Completeness.COMPLETE_WITH_SEMI; return Completeness.COMPLETE_WITH_SEMI;
case PARENS:
if (isDatum) {
return Completeness.COMPLETE_WITH_SEMI;
} else {
return Completeness.DEFINITELY_INCOMPLETE;
}
case DOTSTAR: case DOTSTAR:
if (isImport) { if (isImport) {
return Completeness.COMPLETE_WITH_SEMI; return Completeness.COMPLETE_WITH_SEMI;
@ -780,6 +774,7 @@ class CompletenessAnalyzer {
case ENUM: case ENUM:
case ANNOTATION_TYPE: case ANNOTATION_TYPE:
case INTERFACE: case INTERFACE:
case RECORD:
case METHOD: case METHOD:
return parseDeclaration(); return parseDeclaration();
default: default:

View file

@ -229,6 +229,10 @@ class Eval {
return processClass(userSource, unitTree, compileSourceInt, SubKind.ANNOTATION_TYPE_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
case INTERFACE: case INTERFACE:
return processClass(userSource, unitTree, compileSourceInt, SubKind.INTERFACE_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.INTERFACE_SUBKIND, pt);
case RECORD:
@SuppressWarnings("preview")
List<Snippet> snippets = processClass(userSource, unitTree, compileSourceInt, SubKind.RECORD_SUBKIND, pt);
return snippets;
case METHOD: case METHOD:
return processMethod(userSource, unitTree, compileSourceInt, pt); return processMethod(userSource, unitTree, compileSourceInt, pt);
default: default:

View file

@ -179,7 +179,7 @@ class ReplParser extends JavacParser {
default: default:
JCModifiers mods = modifiersOpt(pmods); JCModifiers mods = modifiersOpt(pmods);
if (token.kind == CLASS if (token.kind == CLASS
|| token.kind == IDENTIFIER && token.name() == token.name().table.names.record || isRecordStart()
|| token.kind == INTERFACE || token.kind == INTERFACE
|| token.kind == ENUM) { || token.kind == ENUM) {
return List.<JCTree>of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); return List.<JCTree>of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));

View file

@ -235,6 +235,23 @@ public abstract class Snippet {
*/ */
ENUM_SUBKIND(Kind.TYPE_DECL), ENUM_SUBKIND(Kind.TYPE_DECL),
/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This enum constant is associated with <i>records</i>, a preview
* feature of the Java language. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* A record declaration.
* A {@code SubKind} of {@link Kind#TYPE_DECL}.
* @jls 8.10 Record Types
* @since 14
*
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS)
RECORD_SUBKIND(Kind.TYPE_DECL),
/** /**
* An annotation interface declaration. A {@code SubKind} of * An annotation interface declaration. A {@code SubKind} of
* {@link Kind#TYPE_DECL}. * {@link Kind#TYPE_DECL}.

View file

@ -23,7 +23,7 @@
/* /*
* @test * @test
* @bug 8149524 8131024 8165211 8080071 8130454 8167343 8129559 8114842 8182268 8223782 * @bug 8149524 8131024 8165211 8080071 8130454 8167343 8129559 8114842 8182268 8223782 8235474
* @summary Test SourceCodeAnalysis * @summary Test SourceCodeAnalysis
* @build KullaTesting TestingInputStream * @build KullaTesting TestingInputStream
* @run testng CompletenessTest * @run testng CompletenessTest
@ -31,11 +31,15 @@
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.util.function.Consumer;
import javax.lang.model.SourceVersion;
import jdk.jshell.JShell;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import jdk.jshell.SourceCodeAnalysis.Completeness; import jdk.jshell.SourceCodeAnalysis.Completeness;
import static jdk.jshell.SourceCodeAnalysis.Completeness.*; import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
import org.testng.annotations.BeforeMethod;
@Test @Test
public class CompletenessTest extends KullaTesting { public class CompletenessTest extends KullaTesting {
@ -82,6 +86,10 @@ public class CompletenessTest extends KullaTesting {
"i >= 0 && Character.isWhitespace(s.charAt(i))", "i >= 0 && Character.isWhitespace(s.charAt(i))",
"int.class", "int.class",
"String.class", "String.class",
"record.any",
"record()",
"record(1)",
"record.length()"
}; };
static final String[] complete_with_semi = new String[] { static final String[] complete_with_semi = new String[] {
@ -123,7 +131,7 @@ public class CompletenessTest extends KullaTesting {
"int[] m = {1, 2}", "int[] m = {1, 2}",
"int[] m = {1, 2}, n = null", "int[] m = {1, 2}, n = null",
"int[] m = {1, 2}, n", "int[] m = {1, 2}, n",
"int[] m = {1, 2}, n = {3, 4}" "int[] m = {1, 2}, n = {3, 4}",
}; };
static final String[] considered_incomplete = new String[] { static final String[] considered_incomplete = new String[] {
@ -193,6 +201,28 @@ public class CompletenessTest extends KullaTesting {
"var v = switch (x) { case ", "var v = switch (x) { case ",
"var v = switch (x) { case 0:", "var v = switch (x) { case 0:",
"var v = switch (x) { case 0: break 12; ", "var v = switch (x) { case 0: break 12; ",
"record D",
"record D(",
"record D(String",
"record D(String i",
"record D(String i,",
"record D(String i, String",
"record D(String i, String j",
"record D(String i)",
"record D(String i, String j)",
"record D(String i) {",
"record D(String i, String j) {",
"static record D",
"static record D(",
"static record D(String",
"static record D(String i",
"static record D(String i,",
"static record D(String i, String",
"static record D(String i, String j",
"static record D(String i)",
"static record D(String i, String j)",
"static record D(String i) {",
"static record D(String i, String j) {",
}; };
static final String[] unknown = new String[] { static final String[] unknown = new String[] {
@ -241,7 +271,7 @@ public class CompletenessTest extends KullaTesting {
} }
public void test_complete() { public void test_complete() {
assertStatus(complete, COMPLETE); assertStatus(complete, COMPLETE);
} }
public void test_expression() { public void test_expression() {
@ -349,4 +379,10 @@ public class CompletenessTest extends KullaTesting {
assertStatus("int[] m = {1, 2}, n = new int[0]; int i;", COMPLETE, assertStatus("int[] m = {1, 2}, n = new int[0]; int i;", COMPLETE,
"int[] m = {1, 2}, n = new int[0];"); "int[] m = {1, 2}, n = new int[0];");
} }
@BeforeMethod
public void setUp() {
setUp(b -> b.compilerOptions("--enable-preview", "-source", String.valueOf(SourceVersion.latest().ordinal())));
}
} }

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019, 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.
*/
/*
* @test
* @bug 8235474
* @summary Tests for evalution of records
* @modules jdk.jshell
* @build KullaTesting TestingInputStream ExpectedDiagnostic
* @run testng RecordsTest
*/
import org.testng.annotations.Test;
import javax.lang.model.SourceVersion;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
@Test
public class RecordsTest extends KullaTesting {
public void testRecordClass() {
assertEval("record R(String s, int i) { }");
assertEquals(varKey(assertEval("R r = new R(\"r\", 42);")).name(), "r");
assertEval("r.s()", "\"r\"");
assertEval("r.i()", "42");
}
public void testRecordField() {
assertEquals(varKey(assertEval("String record = \"\";")).name(), "record");
assertEval("record.length()", "0");
}
public void testRecordMethod() {
assertEquals(methodKey(assertEval("String record(String record) { return record + record; }")).name(), "record");
assertEval("record(\"r\")", "\"rr\"");
assertEval("record(\"r\").length()", "2");
}
@BeforeMethod
public void setUp() {
setUp(b -> b.compilerOptions("--enable-preview", "-source", String.valueOf(SourceVersion.latest().ordinal()))
.remoteVMOptions("--enable-preview"));
}
}