mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
8231461: static/instance overload leads to 'unexpected static method found in unbound lookup' when resolving method reference
Reviewed-by: mcimadamore
This commit is contained in:
parent
42d2d6dcc1
commit
ac4cd2e3c9
8 changed files with 317 additions and 3 deletions
|
@ -112,6 +112,7 @@ public class Resolve {
|
||||||
private final boolean allowLocalVariableTypeInference;
|
private final boolean allowLocalVariableTypeInference;
|
||||||
private final boolean allowYieldStatement;
|
private final boolean allowYieldStatement;
|
||||||
final EnumSet<VerboseResolutionMode> verboseResolutionMode;
|
final EnumSet<VerboseResolutionMode> verboseResolutionMode;
|
||||||
|
final boolean dumpMethodReferenceSearchResults;
|
||||||
|
|
||||||
WriteableScope polymorphicSignatureScope;
|
WriteableScope polymorphicSignatureScope;
|
||||||
|
|
||||||
|
@ -151,6 +152,7 @@ public class Resolve {
|
||||||
polymorphicSignatureScope = WriteableScope.create(syms.noSymbol);
|
polymorphicSignatureScope = WriteableScope.create(syms.noSymbol);
|
||||||
allowModules = Feature.MODULES.allowedInSource(source);
|
allowModules = Feature.MODULES.allowedInSource(source);
|
||||||
allowRecords = Feature.RECORDS.allowedInSource(source);
|
allowRecords = Feature.RECORDS.allowedInSource(source);
|
||||||
|
dumpMethodReferenceSearchResults = options.isSet("debug.dumpMethodReferenceSearchResults");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** error symbols, which are returned when resolution fails
|
/** error symbols, which are returned when resolution fails
|
||||||
|
@ -3090,6 +3092,9 @@ public class Resolve {
|
||||||
Symbol boundSym = lookupMethod(boundEnv, env.tree.pos(),
|
Symbol boundSym = lookupMethod(boundEnv, env.tree.pos(),
|
||||||
site.tsym, boundSearchResolveContext, boundLookupHelper);
|
site.tsym, boundSearchResolveContext, boundLookupHelper);
|
||||||
ReferenceLookupResult boundRes = new ReferenceLookupResult(boundSym, boundSearchResolveContext);
|
ReferenceLookupResult boundRes = new ReferenceLookupResult(boundSym, boundSearchResolveContext);
|
||||||
|
if (dumpMethodReferenceSearchResults) {
|
||||||
|
dumpMethodReferenceSearchResults(referenceTree, boundSearchResolveContext, boundSym, true);
|
||||||
|
}
|
||||||
|
|
||||||
//step 2 - unbound lookup
|
//step 2 - unbound lookup
|
||||||
Symbol unboundSym = methodNotFound;
|
Symbol unboundSym = methodNotFound;
|
||||||
|
@ -3103,6 +3108,9 @@ public class Resolve {
|
||||||
unboundSym = lookupMethod(unboundEnv, env.tree.pos(),
|
unboundSym = lookupMethod(unboundEnv, env.tree.pos(),
|
||||||
site.tsym, unboundSearchResolveContext, unboundLookupHelper);
|
site.tsym, unboundSearchResolveContext, unboundLookupHelper);
|
||||||
unboundRes = new ReferenceLookupResult(unboundSym, unboundSearchResolveContext);
|
unboundRes = new ReferenceLookupResult(unboundSym, unboundSearchResolveContext);
|
||||||
|
if (dumpMethodReferenceSearchResults) {
|
||||||
|
dumpMethodReferenceSearchResults(referenceTree, unboundSearchResolveContext, unboundSym, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//merge results
|
//merge results
|
||||||
|
@ -3126,6 +3134,42 @@ public class Resolve {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dumpMethodReferenceSearchResults(JCMemberReference referenceTree,
|
||||||
|
MethodResolutionContext resolutionContext,
|
||||||
|
Symbol bestSoFar,
|
||||||
|
boolean bound) {
|
||||||
|
ListBuffer<JCDiagnostic> subDiags = new ListBuffer<>();
|
||||||
|
int pos = 0;
|
||||||
|
int mostSpecificPos = -1;
|
||||||
|
for (Candidate c : resolutionContext.candidates) {
|
||||||
|
if (resolutionContext.step != c.step || !c.isApplicable()) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
JCDiagnostic subDiag = null;
|
||||||
|
if (c.sym.type.hasTag(FORALL)) {
|
||||||
|
subDiag = diags.fragment(Fragments.PartialInstSig(c.mtype));
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = subDiag == null ?
|
||||||
|
"applicable.method.found.2" :
|
||||||
|
"applicable.method.found.3";
|
||||||
|
subDiags.append(diags.fragment(key, pos,
|
||||||
|
c.sym.isStatic() ? Fragments.Static : Fragments.NonStatic, c.sym, subDiag));
|
||||||
|
if (c.sym == bestSoFar)
|
||||||
|
mostSpecificPos = pos;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JCDiagnostic main = diags.note(
|
||||||
|
log.currentSource(),
|
||||||
|
referenceTree,
|
||||||
|
"method.ref.search.results.multi",
|
||||||
|
bound ? Fragments.Bound : Fragments.Unbound,
|
||||||
|
referenceTree.toString(), mostSpecificPos);
|
||||||
|
JCDiagnostic d = new JCDiagnostic.MultilineDiagnostic(main, subDiags.toList());
|
||||||
|
log.report(d);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to represent a method reference lookup result. It keeps track of two
|
* This class is used to represent a method reference lookup result. It keeps track of two
|
||||||
* things: (i) the symbol found during a method reference lookup and (ii) the static kind
|
* things: (i) the symbol found during a method reference lookup and (ii) the static kind
|
||||||
|
@ -3280,12 +3324,12 @@ public class Resolve {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ReferenceLookupResult unboundResult(ReferenceLookupResult boundRes, ReferenceLookupResult unboundRes) {
|
ReferenceLookupResult unboundResult(ReferenceLookupResult boundRes, ReferenceLookupResult unboundRes) {
|
||||||
if (boundRes.hasKind(StaticKind.STATIC) &&
|
if (boundRes.isSuccess() && boundRes.sym.isStatic() &&
|
||||||
(!unboundRes.isSuccess() || unboundRes.hasKind(StaticKind.STATIC))) {
|
(!unboundRes.isSuccess() || unboundRes.hasKind(StaticKind.STATIC))) {
|
||||||
//the first search produces a static method and no non-static method is applicable
|
//the first search produces a static method and no non-static method is applicable
|
||||||
//during the second search
|
//during the second search
|
||||||
return boundRes;
|
return boundRes;
|
||||||
} else if (unboundRes.hasKind(StaticKind.NON_STATIC) &&
|
} else if (unboundRes.isSuccess() && !unboundRes.sym.isStatic() &&
|
||||||
(!boundRes.isSuccess() || boundRes.hasKind(StaticKind.NON_STATIC))) {
|
(!boundRes.isSuccess() || boundRes.hasKind(StaticKind.NON_STATIC))) {
|
||||||
//the second search produces a non-static method and no static method is applicable
|
//the second search produces a non-static method and no static method is applicable
|
||||||
//during the first search
|
//during the first search
|
||||||
|
|
|
@ -3066,6 +3066,37 @@ compiler.note.deferred.method.inst=\
|
||||||
compiler.note.verbose.l2m.deduplicate=\
|
compiler.note.verbose.l2m.deduplicate=\
|
||||||
deduplicating lambda implementation method {0}
|
deduplicating lambda implementation method {0}
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# Diagnostics for method reference search
|
||||||
|
# results used by Resolve (debug only)
|
||||||
|
########################################
|
||||||
|
|
||||||
|
# 0: fragment, 1: string, 2: number
|
||||||
|
compiler.note.method.ref.search.results.multi=\
|
||||||
|
{0} search results for {1}, with most specific {2}\n\
|
||||||
|
applicable candidates:
|
||||||
|
|
||||||
|
# 0: number, 1: fragment, 2: symbol
|
||||||
|
compiler.misc.applicable.method.found.2=\
|
||||||
|
#{0} applicable method found: {1} {2}
|
||||||
|
|
||||||
|
# 0: number, 1: fragment, 2: symbol, 3: message segment
|
||||||
|
compiler.misc.applicable.method.found.3=\
|
||||||
|
#{0} applicable method found: {1} {2}\n\
|
||||||
|
({3})
|
||||||
|
|
||||||
|
compiler.misc.static=\
|
||||||
|
static
|
||||||
|
|
||||||
|
compiler.misc.non.static=\
|
||||||
|
non-static
|
||||||
|
|
||||||
|
compiler.misc.bound=\
|
||||||
|
bound
|
||||||
|
|
||||||
|
compiler.misc.unbound=\
|
||||||
|
unbound
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# Diagnostics for where clause implementation
|
# Diagnostics for where clause implementation
|
||||||
# used by the RichDiagnosticFormatter.
|
# used by the RichDiagnosticFormatter.
|
||||||
|
|
|
@ -121,6 +121,10 @@ public class CompilationTestCase extends JavacTemplateTestBase {
|
||||||
return assertCompile(expandMarkers(constructs), this::assertCompileSucceeded, generate);
|
return assertCompile(expandMarkers(constructs), this::assertCompileSucceeded, generate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected File assertOK(Consumer<Diagnostic<?>> diagConsumer, String... constructs) {
|
||||||
|
return assertCompile(expandMarkers(constructs), () -> assertCompileSucceeded(diagConsumer), false);
|
||||||
|
}
|
||||||
|
|
||||||
protected void assertOKWithWarning(String warning, String... constructs) {
|
protected void assertOKWithWarning(String warning, String... constructs) {
|
||||||
assertCompile(expandMarkers(constructs), () -> assertCompileSucceededWithWarning(warning), false);
|
assertCompile(expandMarkers(constructs), () -> assertCompileSucceededWithWarning(warning), false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,10 @@ public class Diagnostics implements javax.tools.DiagnosticListener<JavaFileObjec
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Diagnostic<?>> getAllDiags() {
|
||||||
|
return diags.stream().map(d -> (Diagnostic<?>)d).collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
/** Do the diagnostics contain the specified error key? */
|
/** Do the diagnostics contain the specified error key? */
|
||||||
public boolean containsErrorKey(String key) {
|
public boolean containsErrorKey(String key) {
|
||||||
return diags.stream()
|
return diags.stream()
|
||||||
|
|
|
@ -177,6 +177,13 @@ public abstract class JavacTemplateTestBase {
|
||||||
fail("Expected successful compilation");
|
fail("Expected successful compilation");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Assert that all previous calls to compile() succeeded, also accepts a diagnostics consumer */
|
||||||
|
protected void assertCompileSucceeded(Consumer<Diagnostic<?>> diagConsumer) {
|
||||||
|
if (diags.errorsFound())
|
||||||
|
fail("Expected successful compilation");
|
||||||
|
diags.getAllDiags().stream().forEach(diagConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
/** Assert that all previous calls to compile() succeeded */
|
/** Assert that all previous calls to compile() succeeded */
|
||||||
protected void assertCompileSucceededWithWarning(String warning) {
|
protected void assertCompileSucceededWithWarning(String warning) {
|
||||||
if (diags.errorsFound())
|
if (diags.errorsFound())
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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.note.method.ref.search.results.multi
|
||||||
|
// key: compiler.misc.bound
|
||||||
|
// key: compiler.misc.applicable.method.found.2
|
||||||
|
// key: compiler.misc.static
|
||||||
|
// key: compiler.misc.non.static
|
||||||
|
// key: compiler.misc.unbound
|
||||||
|
// options: --debug=dumpMethodReferenceSearchResults
|
||||||
|
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
class BoundUnboundMethRefSearch {
|
||||||
|
public String foo(Object o) { return "foo"; }
|
||||||
|
public static String foo(String o) { return "bar"; }
|
||||||
|
|
||||||
|
void m() {
|
||||||
|
Function<String, String> f = BoundUnboundMethRefSearch::foo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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.note.method.ref.search.results.multi
|
||||||
|
// key: compiler.misc.bound
|
||||||
|
// key: compiler.misc.applicable.method.found.3
|
||||||
|
// key: compiler.misc.static
|
||||||
|
// key: compiler.misc.partial.inst.sig
|
||||||
|
// key: compiler.misc.unbound
|
||||||
|
// options: --debug=dumpMethodReferenceSearchResults
|
||||||
|
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
class BoundUnboundMethRefSearch2 {
|
||||||
|
interface SAM <T> {
|
||||||
|
boolean test(T n, T m);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> boolean foo(T x, T y) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bar() {
|
||||||
|
SAM <Integer> mRef = BoundUnboundMethRefSearch2::<Integer>foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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 8231461
|
||||||
|
* @summary static/instance overload leads to 'unexpected static method found in unbound lookup' when resolving method reference
|
||||||
|
* @library /lib/combo /tools/lib /tools/javac/lib
|
||||||
|
* @modules
|
||||||
|
* jdk.compiler/com.sun.tools.javac.api
|
||||||
|
* jdk.compiler/com.sun.tools.javac.util
|
||||||
|
* @run testng BoundUnboundSearchTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
|
||||||
|
import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
|
||||||
|
import com.sun.tools.javac.util.Assert;
|
||||||
|
import com.sun.tools.javac.util.JCDiagnostic;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import tools.javac.combo.CompilationTestCase;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public class BoundUnboundSearchTest extends CompilationTestCase {
|
||||||
|
static final String TEMPLATE =
|
||||||
|
"""
|
||||||
|
import java.util.function.*;
|
||||||
|
class Test {
|
||||||
|
#CANDIDATES
|
||||||
|
void m() {
|
||||||
|
Function<String, String> f = Test::foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
public BoundUnboundSearchTest() {
|
||||||
|
setDefaultFilename("Test.java");
|
||||||
|
setCompileOptions(new String[]{"--debug=dumpMethodReferenceSearchResults"});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<Diagnostic<?>> getDiagConsumer(final int boundCandidate, final int unboundCandidate) {
|
||||||
|
return diagWrapper -> {
|
||||||
|
JCDiagnostic diagnostic = ((DiagnosticSourceUnwrapper)diagWrapper).d;
|
||||||
|
Object[] args = diagnostic.getArgs();
|
||||||
|
if (args[0].toString().equals("bound")) {
|
||||||
|
Assert.check(args[2].equals(boundCandidate));
|
||||||
|
} else if (args[0].toString().equals("unbound")) {
|
||||||
|
Assert.check(args[2].equals(unboundCandidate));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
assertOK(
|
||||||
|
getDiagConsumer(0, -1),
|
||||||
|
TEMPLATE.replaceFirst("#CANDIDATES",
|
||||||
|
"""
|
||||||
|
public String foo(Object o) { return "foo"; } // candidate 0
|
||||||
|
public static String foo(String o) { return "bar"; } // candidate 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertOK(
|
||||||
|
getDiagConsumer(0, -1),
|
||||||
|
TEMPLATE.replaceFirst("#CANDIDATES",
|
||||||
|
"""
|
||||||
|
public static String foo(Object o) { return "foo"; } // candidate 0
|
||||||
|
public static String foo(String o) { return "bar"; } // candidate 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFail("compiler.err.prob.found.req",
|
||||||
|
getDiagConsumer(0, -1),
|
||||||
|
TEMPLATE.replaceFirst("#CANDIDATES",
|
||||||
|
"""
|
||||||
|
public static String foo(Object o) { return "foo"; } // candidate 0
|
||||||
|
public String foo(String o) { return "bar"; } // candidate 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFail("compiler.err.prob.found.req",
|
||||||
|
getDiagConsumer(0, -1),
|
||||||
|
TEMPLATE.replaceFirst("#CANDIDATES",
|
||||||
|
"""
|
||||||
|
public String foo(Object o) { return "foo"; } // candidate 0
|
||||||
|
public String foo(String o) { return "bar"; } // candidate 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertFail("compiler.err.invalid.mref",
|
||||||
|
getDiagConsumer(-1, -1),
|
||||||
|
"""
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public String foo(Object o) { return "foo"; }
|
||||||
|
public static String foo(String o) { return "bar"; }
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
// method bar doesn't exist
|
||||||
|
Function<String, String> f = Test::bar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue