Skip to content

Commit

Permalink
[JEP-445] Flow, validation, lookup for unnamed classes
Browse files Browse the repository at this point in the history
  • Loading branch information
mickaelistria committed Oct 27, 2023
1 parent 1c5f361 commit 8e5e4ac
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2553,6 +2553,17 @@ public interface IProblem {
* @noreference preview feature
*/
int CannotInferRecordPatternTypes = PreviewRelated + 1940;

/**
* @since 3.36
* @noreference preview feature
*/
int unnamedIsMissingMainMethod = PreviewRelated + 1942;
/**
* @since 3.36
* @noreference preview feature
*/
int topLevelWithUnnamed = PreviewRelated + 1944;

/**
* @since 3.36
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
Expand Down Expand Up @@ -133,6 +134,10 @@ public void analyseCode() {
return;
try {
if (this.types != null) {
if (this.types.length > 1 &&
Stream.of(this.types).anyMatch(UnnamedClass.class::isInstance)) {
this.problemReporter.cannotDefineTopLevelTypesWithUnnamed();
}
for (int i = 0, count = this.types.length; i < count; i++) {
this.types[i].analyseCode(this.scope);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiPredicate;

Expand Down Expand Up @@ -443,4 +444,13 @@ public void traverse(
public TypeParameter[] typeParameters() {
return this.typeParameters;
}

public boolean isMainMethodCandidate() {
return "main".equals(new String(this.selector)) && //$NON-NLS-1$
(this.arguments == null || this.arguments.length == 0 ||
(this.arguments.length == 1 &&
this.arguments[0].type.getTypeBinding(this.scope) instanceof ReferenceBinding typeRef &&
typeRef.dimensions() == 1 &&
Arrays.equals("java.lang.String".toCharArray(), CharOperation.concatWith(typeRef.compoundName, '.')))); //$NON-NLS-1$
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import java.util.Arrays;

import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;

/**
* Represents an unnamed class as defined in JEP 445
Expand All @@ -24,4 +27,19 @@ public UnnamedClass(CompilationResult result) {
this.name = "<unnamed class>".toCharArray(); //$NON-NLS-1$
}


@Override
public void analyseCode(CompilationUnitScope unitScope) {
super.analyseCode(unitScope);
if (this.ignoreFurtherInvestigation)
return;
if (Arrays.stream(this.methods)
.filter(MethodDeclaration.class::isInstance)
.map(MethodDeclaration.class::cast)
.noneMatch(MethodDeclaration::isMainMethodCandidate)) {
unitScope.problemReporter().unnamedClassMustHaveMainMethod();
this.ignoreFurtherInvestigation = true;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,9 @@ public ReferenceBinding findType(
if (declarationPackage != invocationPackage && !typeBinding.canBeSeenBy(invocationPackage))
return new ProblemReferenceBinding(new char[][]{typeName}, typeBinding, ProblemReasons.NotVisible);
}
if (typeBinding instanceof SourceTypeBinding sourceType && sourceType.scope != null && sourceType.scope.referenceContext instanceof UnnamedClass) {
return null; // make unnamed not resolvable by name
}
return typeBinding;
}

Expand Down Expand Up @@ -3529,9 +3532,10 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) {
PackageBinding currentPackage = unitScope.fPackage;
unitScope.recordReference(currentPackage.compoundName, name);
Binding binding = currentPackage.getTypeOrPackage(name, module(), false);
if (binding instanceof ReferenceBinding) {
ReferenceBinding referenceType = (ReferenceBinding) binding;
if ((referenceType.tagBits & TagBits.HasMissingType) == 0) {
if (binding instanceof ReferenceBinding referenceType) {
if (referenceType instanceof SourceTypeBinding sourceType && sourceType.unnamedClass) {
return new ProblemReferenceBinding(new char[][] {name}, null, ProblemReasons.NotAccessible);
} else if ((referenceType.tagBits & TagBits.HasMissingType) == 0) {
if (typeOrPackageCache != null)
typeOrPackageCache.put(name, referenceType);
return referenceType; // type is always visible to its own package
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnnamedClass;
import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationPosition;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
Expand Down Expand Up @@ -138,9 +139,13 @@ public class SourceTypeBinding extends ReferenceBinding {
public boolean isVarArgs = false; // for record declaration
private FieldBinding[] implicitComponentFields; // cache
private MethodBinding[] recordComponentAccessors = null; // hash maybe an overkill
public final boolean unnamedClass;

public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) {
this.compoundName = compoundName;
this.unnamedClass = scope.referenceContext instanceof UnnamedClass;
this.compoundName = scope.referenceContext instanceof UnnamedClass unnamed
? new char[][] { unnamed.getCompilationUnitDeclaration().getMainTypeName() }
: compoundName;
this.fPackage = fPackage;
this.fileName = scope.referenceCompilationUnit().getFileName();
this.modifiers = scope.referenceContext.modifiers;
Expand All @@ -163,6 +168,7 @@ public SourceTypeBinding(SourceTypeBinding prototype) {
this.prototype = prototype.prototype;
this.prototype.tagBits |= TagBits.HasAnnotatedVariants;
this.tagBits &= ~TagBits.HasAnnotatedVariants;
this.unnamedClass = prototype.unnamedClass;

this.superclass = prototype.superclass;
this.superInterfaces = prototype.superInterfaces;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12502,4 +12502,22 @@ public boolean scheduleProblemForContext(Runnable problemComputation) {
public void close() {
this.referenceContext = null;
}

public void unnamedClassMustHaveMainMethod() {
this.handle(
IProblem.unnamedIsMissingMainMethod,
NoArgument,
NoArgument,
0,
0);
}

public void cannotDefineTopLevelTypesWithUnnamed() {
this.handle(
IProblem.topLevelWithUnnamed,
NoArgument,
NoArgument,
0,
0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,9 @@
1916 = A case label guard cannot have a constant expression with value as 'false'
1940 = Cannot infer record pattern types for {0}
1941 = Syntax error, record patterns are not allowed here
1942 = unnamed class does not have main method in the form of void main() or void main(String[] args)
1943 = Unnamed class requires Java 21 and preview features
1944 = No other top-level types allowed with unnamed class


1990 = Access to {1}({2}) from the type {0} is emulated by a synthetic accessor method
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.jdt.core.tests.compiler.regression;

import java.io.File;

import junit.framework.Test;

public class BatchCompilerTest_21 extends AbstractBatchCompilerTest {

/**
* This test suite only needs to be run on one compliance.
*
* @see TestAll
*/
public static Test suite() {
return buildMinimalComplianceTestSuite(testClass(), F_21);
}

public static Class<BatchCompilerTest_21> testClass() {
return BatchCompilerTest_21.class;
}

public BatchCompilerTest_21(String name) {
super(name);
}

public void testJEP445() throws Exception {
this.runConformTest(
new String[] {
"src/X.java",
"""
void main() {hello();}
void hello() {new Y().hello();}
class Y {
public void hello() {
System.out.print("hello");
}
}
"""
},
'"' + OUTPUT_DIR + File.separator + "src/X.java" + '"'
+ " -sourcepath " + '"' + OUTPUT_DIR + File.separator + "src" + '"'
+ " --enable-preview -source 21 -warn:none"
+ " -d " + '"' + OUTPUT_DIR + File.separator + "bin" + '"',
"",
"",
true);
File targetDir = new File(OUTPUT_DIR + File.separator + "bin");
assertTrue(new File(targetDir, "X.class").isFile());
assertTrue(new File(targetDir, "X$Y.class").isFile());
this.verifier.execute("X", new String[] {targetDir.getAbsolutePath()}, new String[0], new String[] {"--enable-preview"});
assertEquals("Incorrect output", "hello", this.verifier.getExecutionOutput());
}

public void testCannotResolveName() throws Exception {
this.runNegativeTest(
new String[] {
"src/X.java",
"""
void main() {new X().hello();}
void hello() {System.out.print("hello");}
"""
},
'"' + OUTPUT_DIR + File.separator + "src/X.java" + '"'
+ " -sourcepath " + '"' + OUTPUT_DIR + File.separator + "src" + '"'
+ " --enable-preview -source 21 -warn:none"
+ " -d " + '"' + OUTPUT_DIR + File.separator + "bin" + '"',
"",
"""
----------
1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/X.java (at line 1)
void main() {new X().hello();}
^
The type X is not accessible
----------
1 problem (1 error)
"""
/* javac says:
X.java:1: error: cannot find symbol
void main(){new X().toString();}
^
symbol: class X
location: class X*/,
true);
}

public void testWithoutMain() throws Exception {
this.runNegativeTest(
new String[] {
"src/X.java",
"""
void hello() {System.out.print("hello");}
"""
},
'"' + OUTPUT_DIR + File.separator + "src/X.java" + '"'
+ " -sourcepath " + '"' + OUTPUT_DIR + File.separator + "src" + '"'
+ " --enable-preview -source 21 -warn:none"
+ " -d " + '"' + OUTPUT_DIR + File.separator + "bin" + '"',
"",
"""
----------
1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/X.java (at line 1)
void hello() {System.out.print("hello");}
^
unnamed class does not have main method in the form of void main() or void main(String[] args)
----------
1 problem (1 error)
"""
/* javac says:
X.java:1: error: unnamed class does not have main method in the form of void main() or void main(String[] args)
void hello(){}
^
*/,
true);
}


public void testRedefineUnnamed() {
this.runNegativeTest(
new String[] { "src/X.java",
"""
public class X {}
void main() {}
""" },
'"' + OUTPUT_DIR + File.separator + "src/X.java" + '"'
+ " -sourcepath " + '"' + OUTPUT_DIR + File.separator + "src" + '"'
+ " --enable-preview -source 21 -warn:none"
+ " -d " + '"' + OUTPUT_DIR + File.separator + "bin" + '"',
"",
"""
----------
1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/X.java (at line 0)
public class X {}
^
No other top-level types allowed with unnamed class
----------
2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/X.java (at line 1)
public class X {}
^
The type X is already defined
----------
2 problems (2 errors)
"""
/* javac says:
X.java:1: error: class X is already defined in package unnamed package
public class X {
^
1 error
error: compilation failed
*/,
true);
}

}

0 comments on commit 8e5e4ac

Please sign in to comment.