Skip to content

Commit 0eb7ed3

Browse files
More efficient, more modern, fast failing version of computation of the abstract contracts of an interface while deriving SAM (eclipse-jdt#4232)
* Fixes eclipse-jdt#4230 --------- Co-authored-by: Florian Schwabe <florian.schwabe@vector.com>
1 parent f777d85 commit 0eb7ed3

File tree

3 files changed

+67
-106
lines changed

3 files changed

+67
-106
lines changed

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/IntersectionTypeBinding18.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323

2424
package org.eclipse.jdt.internal.compiler.lookup;
2525

26+
import java.util.Arrays;
2627
import java.util.Set;
28+
import java.util.stream.Stream;
2729
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
2830
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
2931

@@ -67,22 +69,10 @@ public TypeBinding clone(TypeBinding enclosingType) {
6769
}
6870

6971
@Override
70-
protected MethodBinding[] getInterfaceAbstractContracts(Scope scope, boolean replaceWildcards, boolean filterDefaultMethods) throws InvalidBindingException {
71-
int typesLength = this.intersectingTypes.length;
72-
MethodBinding[][] methods = new MethodBinding[typesLength][];
73-
int contractsLength = 0;
74-
for (int i = 0; i < typesLength; i++) {
75-
methods[i] = this.intersectingTypes[i].getInterfaceAbstractContracts(scope, replaceWildcards, true);
76-
contractsLength += methods[i].length;
77-
}
78-
MethodBinding[] contracts = new MethodBinding[contractsLength];
79-
int idx = 0;
80-
for (int i = 0; i < typesLength; i++) {
81-
int len = methods[i].length;
82-
System.arraycopy(methods[i], 0, contracts, idx, len);
83-
idx += len;
84-
}
85-
return contracts;
72+
protected Stream<MethodBinding> collateFunctionalInterfaceContracts(Scope scope, boolean replaceWildcards, Set<ReferenceBinding> visitedInterfaces) throws DysfunctionalInterfaceException {
73+
return Arrays.stream(this.intersectingTypes).flatMap( //
74+
intersectingType -> intersectingType.collateFunctionalInterfaceContracts(scope, replaceWildcards, visitedInterfaces) //
75+
);
8676
}
8777

8878
@Override

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.List;
5454
import java.util.Map;
5555
import java.util.Set;
56+
import java.util.stream.Stream;
5657
import org.eclipse.jdt.core.compiler.CharOperation;
5758
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
5859
import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching;
@@ -1730,26 +1731,28 @@ public FieldBinding[] unResolvedFields() {
17301731
}
17311732

17321733
@Override
1733-
protected MethodBinding[] getInterfaceAbstractContracts(Scope scope, boolean replaceWildcards, boolean filterDefaultMethods) throws InvalidBindingException {
1734+
protected Stream<MethodBinding> collateFunctionalInterfaceContracts(Scope scope, boolean replaceWildcards, Set<ReferenceBinding> visitedInterfaces) throws DysfunctionalInterfaceException {
17341735
if (replaceWildcards) {
17351736
TypeBinding[] types = getNonWildcardParameters(scope);
17361737
if (types == null)
1737-
return new MethodBinding[] { new ProblemMethodBinding(TypeConstants.ANONYMOUS_METHOD, null, ProblemReasons.NotAWellFormedParameterizedType) };
1738+
throw DYSFUNCTIONAL_INTERFACE_EXCEPTION;
1739+
17381740
for (int i = 0; i < types.length; i++) {
17391741
if (TypeBinding.notEquals(types[i], this.arguments[i])) {
17401742
// non-wildcard parameterization differs from this, so use it:
17411743
ParameterizedTypeBinding declaringType = scope.environment().createParameterizedType(this.type, types, this.type.enclosingType());
17421744
TypeVariableBinding [] typeParameters = this.type.typeVariables();
17431745
for (int j = 0, length = typeParameters.length; j < length; j++) {
17441746
if (!typeParameters[j].boundCheck(declaringType, types[j], scope, null).isOKbyJLS())
1745-
return new MethodBinding[] { new ProblemMethodBinding(TypeConstants.ANONYMOUS_METHOD, null, ProblemReasons.NotAWellFormedParameterizedType) };
1747+
throw DYSFUNCTIONAL_INTERFACE_EXCEPTION;
17461748
}
1747-
return declaringType.getInterfaceAbstractContracts(scope, replaceWildcards, filterDefaultMethods);
1749+
return declaringType.collateFunctionalInterfaceContracts(scope, replaceWildcards, visitedInterfaces);
17481750
}
17491751
}
17501752
}
1751-
return super.getInterfaceAbstractContracts(scope, replaceWildcards, filterDefaultMethods);
1753+
return super.collateFunctionalInterfaceContracts(scope, replaceWildcards, visitedInterfaces);
17521754
}
1755+
17531756
@Override
17541757
public MethodBinding getSingleAbstractMethod(final Scope scope, boolean replaceWildcards) {
17551758
return getSingleAbstractMethod(scope, replaceWildcards, -1, -1 /* do not capture */);

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java

Lines changed: 53 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import java.util.List;
6161
import java.util.Map;
6262
import java.util.Set;
63+
import java.util.function.Predicate;
64+
import java.util.stream.Stream;
6365
import org.eclipse.jdt.core.compiler.CharOperation;
6466
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
6567
import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
@@ -96,6 +98,8 @@ abstract public class ReferenceBinding extends TypeBinding {
9698
int typeBits; // additional bits characterizing this type
9799
protected MethodBinding [] singleAbstractMethod;
98100

101+
protected static final DysfunctionalInterfaceException DYSFUNCTIONAL_INTERFACE_EXCEPTION = new DysfunctionalInterfaceException("Not a functional interface"); //$NON-NLS-1$
102+
99103
public static final ReferenceBinding LUB_GENERIC = new ReferenceBinding() { /* used for lub computation */
100104
{ this.id = TypeIds.T_undefined; }
101105
@Override
@@ -2322,93 +2326,58 @@ public void detectWrapperResource() {
23222326
}
23232327
}
23242328

2325-
protected MethodBinding [] getInterfaceAbstractContracts(Scope scope, boolean replaceWildcards, boolean filterDefaultMethods) throws InvalidBindingException {
2326-
2327-
if (!isInterface() || !isValidBinding()) {
2328-
throw new InvalidBindingException("Not a functional interface"); //$NON-NLS-1$
2329-
}
2330-
2331-
MethodBinding [] methods = methods();
2332-
MethodBinding [] contracts = new MethodBinding[0];
2333-
int contractsCount = 0;
2334-
int contractsLength = 0;
2335-
2336-
ReferenceBinding [] superInterfaces = superInterfaces();
2337-
for (ReferenceBinding superInterface : superInterfaces) {
2338-
// filterDefaultMethods=false => keep default methods needed to filter out any abstract methods they may override:
2339-
MethodBinding [] superInterfaceContracts = superInterface.getInterfaceAbstractContracts(scope, replaceWildcards, false);
2340-
final int superInterfaceContractsLength = superInterfaceContracts == null ? 0 : superInterfaceContracts.length;
2341-
if (superInterfaceContractsLength == 0) continue;
2342-
if (contractsLength < contractsCount + superInterfaceContractsLength) {
2343-
System.arraycopy(contracts, 0, contracts = new MethodBinding[contractsLength = contractsCount + superInterfaceContractsLength], 0, contractsCount);
2344-
}
2345-
System.arraycopy(superInterfaceContracts, 0, contracts, contractsCount, superInterfaceContractsLength);
2346-
contractsCount += superInterfaceContractsLength;
2347-
}
2329+
private MethodBinding[] getFunctionalInterfaceAbstractContracts(Scope scope, boolean replaceWildcards) throws DysfunctionalInterfaceException {
23482330

23492331
LookupEnvironment environment = scope.environment();
2350-
for (int i = 0, length = methods == null ? 0 : methods.length; i < length; i++) {
2351-
final MethodBinding method = methods[i];
2352-
if (method == null || method.isStatic() || method.redeclaresPublicObjectMethod(scope) || method.isPrivate())
2353-
continue;
2354-
if (!method.isValidBinding())
2355-
throw new InvalidBindingException("Not a functional interface"); //$NON-NLS-1$
2356-
for (int j = 0; j < contractsCount;) {
2357-
if ( contracts[j] != null && MethodVerifier.doesMethodOverride(method, contracts[j], environment)) {
2358-
contractsCount--;
2359-
// abstract method from super type overridden by present interface ==> contracts[j] = null;
2360-
if (j < contractsCount) {
2361-
System.arraycopy(contracts, j+1, contracts, j, contractsCount - j);
2362-
continue;
2363-
}
2364-
}
2365-
j++;
2366-
}
2367-
if (filterDefaultMethods && method.isDefaultMethod())
2368-
continue; // skip default method itself
2369-
if (contractsCount == contractsLength) {
2370-
System.arraycopy(contracts, 0, contracts = new MethodBinding[contractsLength += 16], 0, contractsCount);
2371-
}
2372-
if(environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) {
2373-
ImplicitNullAnnotationVerifier.ensureNullnessIsKnown(method, scope);
2374-
}
2375-
contracts[contractsCount++] = method;
2376-
}
2377-
// check mutual overriding of inherited methods (i.e., not from current type):
2378-
for (int i = 0; i < contractsCount; i++) {
2379-
MethodBinding contractI = contracts[i];
2380-
if (TypeBinding.equalsEquals(contractI.declaringClass, this))
2381-
continue;
2382-
for (int j = 0; j < contractsCount; j++) {
2383-
MethodBinding contractJ = contracts[j];
2384-
if (i == j || TypeBinding.equalsEquals(contractJ.declaringClass, this))
2385-
continue;
2386-
if (contractI == contractJ || MethodVerifier.doesMethodOverride(contractI, contractJ, environment)) {
2387-
contractsCount--;
2388-
// abstract method from one super type overridden by other super interface ==> contracts[j] = null;
2389-
if (j < contractsCount) {
2390-
System.arraycopy(contracts, j+1, contracts, j, contractsCount - j);
2391-
}
2392-
j--;
2393-
if (j < i)
2394-
i--;
2395-
continue;
2332+
boolean isAnnotationBasedNullAnalysisEnabled = environment.globalOptions.isAnnotationBasedNullAnalysisEnabled;
2333+
MethodBinding samCandidate = null;
2334+
2335+
MethodBinding[] methods = collateFunctionalInterfaceContracts(scope, replaceWildcards, new HashSet<>())
2336+
.sorted((m1, m2) -> CharOperation.compareTo(m1.selector, m2.selector))
2337+
.toArray(MethodBinding []::new);
2338+
2339+
for (int i = 0, length = methods.length; i < length; i++) {
2340+
MethodBinding method = methods[i];
2341+
for (int j = i + 1; j < length; j++) {
2342+
MethodBinding otherMethod = methods[j];
2343+
if (method.selector.length != otherMethod.selector.length || !CharOperation.equals(method.selector, otherMethod.selector))
2344+
break; // Skip comparing apple to oranges (input sorted by selector, so no viable overrides past this point)
2345+
if (MethodVerifier.doesMethodOverride(otherMethod, method, environment)) {
2346+
methods[i] = method = null;
2347+
break; // Skip comparing rotten apple with remaining lot: is overridden already, no use checking if is overridden also by another
23962348
}
23972349
}
2398-
if (filterDefaultMethods && contractI.isDefaultMethod()) {
2399-
contractsCount--;
2400-
// remove default method after it has eliminated any matching abstract methods from contracts
2401-
if (i < contractsCount) {
2402-
System.arraycopy(contracts, i+1, contracts, i, contractsCount - i);
2403-
}
2404-
i--;
2350+
if (method != null && method.isAbstract()) {
2351+
if (samCandidate == null)
2352+
samCandidate = method;
2353+
else if (!CharOperation.equals(samCandidate.selector, method.selector) || samCandidate.parameters.length != method.parameters.length)
2354+
throw DYSFUNCTIONAL_INTERFACE_EXCEPTION;
2355+
if (isAnnotationBasedNullAnalysisEnabled)
2356+
ImplicitNullAnnotationVerifier.ensureNullnessIsKnown(method, scope);
24052357
}
24062358
}
2407-
if (contractsCount < contractsLength) {
2408-
System.arraycopy(contracts, 0, contracts = new MethodBinding[contractsCount], 0, contractsCount);
2409-
}
2410-
return contracts;
2359+
return Arrays.stream(methods).filter(m -> m != null && m.isAbstract()).toArray(MethodBinding []::new);
24112360
}
2361+
2362+
protected Stream<MethodBinding> collateFunctionalInterfaceContracts(Scope scope, boolean replaceWildcards, Set<ReferenceBinding> visitedInterfaces) throws DysfunctionalInterfaceException {
2363+
2364+
if (!isInterface() || !isValidBinding())
2365+
throw DYSFUNCTIONAL_INTERFACE_EXCEPTION;
2366+
2367+
Predicate<MethodBinding> isContractual = m -> { // select valid public abstract || default methods
2368+
if (m == null || m.isStatic() || m.redeclaresPublicObjectMethod(scope) || m.isPrivate())
2369+
return false;
2370+
if (!m.isValidBinding())
2371+
throw DYSFUNCTIONAL_INTERFACE_EXCEPTION;
2372+
return true;
2373+
};
2374+
2375+
return Stream.concat( //
2376+
Arrays.stream(superInterfaces()).filter(iface->visitedInterfaces.add(iface)).flatMap(superInterface -> superInterface.collateFunctionalInterfaceContracts(scope, replaceWildcards, visitedInterfaces)), //
2377+
Arrays.stream(methods()).filter(isContractual)
2378+
);
2379+
}
2380+
24122381
@Override
24132382
public MethodBinding getSingleAbstractMethod(Scope scope, boolean replaceWildcards) {
24142383

@@ -2426,7 +2395,7 @@ public MethodBinding getSingleAbstractMethod(Scope scope, boolean replaceWildcar
24262395
scope.compilationUnitScope().recordQualifiedReference(this.compoundName);
24272396
MethodBinding[] methods = null;
24282397
try {
2429-
methods = getInterfaceAbstractContracts(scope, replaceWildcards, true);
2398+
methods = getFunctionalInterfaceAbstractContracts(scope, replaceWildcards);
24302399
if (methods == null || methods.length == 0)
24312400
return this.singleAbstractMethod[index] = samProblemBinding;
24322401
int contractParameterLength = 0;
@@ -2442,7 +2411,7 @@ public MethodBinding getSingleAbstractMethod(Scope scope, boolean replaceWildcar
24422411
return this.singleAbstractMethod[index] = samProblemBinding;
24432412
}
24442413
}
2445-
} catch (InvalidBindingException e) {
2414+
} catch (DysfunctionalInterfaceException e) {
24462415
return this.singleAbstractMethod[index] = samProblemBinding;
24472416
}
24482417
if (methods.length == 1)
@@ -2677,10 +2646,9 @@ public boolean isDisjointFrom(ReferenceBinding that) {
26772646
}
26782647
}
26792648
}
2680-
static class InvalidBindingException extends Exception {
2649+
static class DysfunctionalInterfaceException extends RuntimeException {
26812650
private static final long serialVersionUID = 1L;
2682-
2683-
InvalidBindingException(String message) {
2651+
DysfunctionalInterfaceException(String message) {
26842652
super(message);
26852653
}
26862654
}

0 commit comments

Comments
 (0)