Skip to content

Commit

Permalink
DuplicateExecutables in JvmGenericTypeValidator
Browse files Browse the repository at this point in the history
  • Loading branch information
LorenzoBettini committed Feb 5, 2024
1 parent 3da692d commit ec5270f
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ public void testNoConflictWithStaticMethods_04() throws Exception {
_builder.append("}");
_builder.newLine();
final XtendFile file = this.file(_builder.toString());
this._validationTestHelper.assertError(file, XtendPackage.Literals.XTEND_FUNCTION, IssueCodes.DUPLICATE_METHOD,
this._validationTestHelper.assertError(file, XtendPackage.Literals.XTEND_FUNCTION, org.eclipse.xtext.xbase.validation.IssueCodes.DUPLICATE_METHOD,
"The static method getTheNumber() cannot hide the instance method getTheNumber() of type IX.");
}

Expand Down Expand Up @@ -640,7 +640,7 @@ public void testNoConflictWithStaticMethods_05() throws Exception {
_builder.append("}");
_builder.newLine();
final XtendFile file = this.file(_builder.toString());
this._validationTestHelper.assertError(file, XtendPackage.Literals.XTEND_FUNCTION, IssueCodes.DUPLICATE_METHOD,
this._validationTestHelper.assertError(file, XtendPackage.Literals.XTEND_FUNCTION, org.eclipse.xtext.xbase.validation.IssueCodes.DUPLICATE_METHOD,
"The static method getTheNumber(Number) cannot hide the instance method getTheNumber(Number) of type IX.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ private IssueCodes() {
public static final String CREATE_FUNCTIONS_MUST_NOT_BE_ABSTRACT = "create_functions_must_not_be_abstract";
public static final String WRONG_PACKAGE = ISSUE_CODE_PREFIX + "wrong_package";
public static final String WRONG_FILE = ISSUE_CODE_PREFIX + "wrong_file";
public static final String DUPLICATE_METHOD = ISSUE_CODE_PREFIX + "duplicate_method";
public static final String CONFLICTING_DEFAULT_METHODS = ISSUE_CODE_PREFIX + "conflicting_default_methods";
public static final String MISSING_ABSTRACT = ISSUE_CODE_PREFIX + "missing_abstract";
public static final String MISSING_ABSTRACT_IN_ANONYMOUS = ISSUE_CODE_PREFIX + "missing_abstract_in_anonymous";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.lang.annotation.ElementType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -125,8 +124,6 @@
import org.eclipse.xtext.xbase.typesystem.IResolvedTypes;
import org.eclipse.xtext.xbase.typesystem.override.ConflictingDefaultOperation;
import org.eclipse.xtext.xbase.typesystem.override.IOverrideCheckResult.OverrideCheckDetails;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedConstructor;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedExecutable;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedOperation;
import org.eclipse.xtext.xbase.typesystem.override.OverrideHelper;
import org.eclipse.xtext.xbase.typesystem.override.ResolvedFeatures;
Expand Down Expand Up @@ -640,91 +637,11 @@ public void checkDuplicateAndOverriddenFunctions(XtendTypeDeclaration xtendType)
ResolvedFeatures resolvedFeatures = overrideHelper.getResolvedFeatures(inferredType, targetVersion);

Set<EObject> flaggedOperations = Sets.newHashSet();
doCheckDuplicateExecutables((JvmGenericType) inferredType, resolvedFeatures, flaggedOperations);
doCheckOverriddenMethods(xtendType, (JvmGenericType) inferredType, resolvedFeatures, flaggedOperations);
doCheckFunctionOverrides(resolvedFeatures, flaggedOperations);
}
}

protected void doCheckDuplicateExecutables(JvmGenericType inferredType, final ResolvedFeatures resolvedFeatures, Set<EObject> flaggedOperations) {
List<IResolvedOperation> declaredOperations = resolvedFeatures.getDeclaredOperations();
doCheckDuplicateExecutables(inferredType, declaredOperations, new Function<String, List<IResolvedOperation>>() {
@Override
public List<IResolvedOperation> apply(String erasedSignature) {
return resolvedFeatures.getDeclaredOperations(erasedSignature);
}
}, flaggedOperations);
final List<IResolvedConstructor> declaredConstructors = resolvedFeatures.getDeclaredConstructors();
doCheckDuplicateExecutables(inferredType, declaredConstructors, new Function<String, List<IResolvedConstructor>>() {
@Override
public List<IResolvedConstructor> apply(String erasedSignature) {
if (declaredConstructors.size() == 1) {
if (erasedSignature.equals(declaredConstructors.get(0).getResolvedErasureSignature())) {
return declaredConstructors;
}
return Collections.emptyList();
}
List<IResolvedConstructor> result = Lists.newArrayListWithCapacity(declaredConstructors.size());
for(IResolvedConstructor constructor: declaredConstructors) {
if (erasedSignature.equals(constructor.getResolvedErasureSignature())) {
result.add(constructor);
}
}
return result;
}
}, flaggedOperations);
}

protected <Executable extends IResolvedExecutable> void doCheckDuplicateExecutables(JvmGenericType inferredType,
List<Executable> declaredOperations, Function<String, List<Executable>> bySignature, Set<EObject> flaggedOperations) {
Set<Executable> processed = Sets.newHashSet();
for(Executable declaredExecutable: declaredOperations) {
if (!processed.contains(declaredExecutable)) {
List<Executable> sameErasure = bySignature.apply(declaredExecutable.getResolvedErasureSignature());
if (sameErasure.size() > 1) {
Multimap<String, Executable> perSignature = HashMultimap.create(sameErasure.size(), 2);
outer: for(Executable executable: sameErasure) {
for(LightweightTypeReference parameterType: executable.getResolvedParameterTypes()) {
if (parameterType.isUnknown())
continue outer;
}
perSignature.put(executable.getResolvedSignature(), executable);
}
if (perSignature.size() > 1) {
for(Collection<Executable> sameSignature: perSignature.asMap().values()) {
for(Executable operationWithSameSignature: sameSignature) {
JvmExecutable executable = operationWithSameSignature.getDeclaration();
EObject otherSource = associations.getPrimarySourceElement(executable);
if (flaggedOperations.add(otherSource)) {
if (sameSignature.size() > 1) {
error("Duplicate " + typeLabel(executable) + " " + operationWithSameSignature.getSimpleSignature()
+ " in type " + inferredType.getSimpleName(), otherSource,
nameFeature(otherSource), DUPLICATE_METHOD);
} else {
error("The " + typeLabel(executable) + " " + operationWithSameSignature.getSimpleSignature()
+ " has the same erasure "
+ operationWithSameSignature.getResolvedErasureSignature()
+ " as another " + typeLabel(executable) + " in type " + inferredType.getSimpleName(), otherSource,
nameFeature(otherSource), DUPLICATE_METHOD);
}
}
}
}
}
}
}
}
}

protected String typeLabel(JvmExecutable executable) {
if (executable instanceof JvmOperation)
return "method";
else if(executable instanceof JvmConstructor)
return "constructor";
else
return "?";
}

protected EStructuralFeature nameFeature(EObject member) {
if (member instanceof XtendFunction)
return XTEND_FUNCTION__NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ public class IssueCodes extends org.eclipse.xtext.validation.IssueCodes {
* @since 2.34
*/
public static final String DUPLICATE_FIELD = ISSUE_CODE_PREFIX + "duplicate_field";
/**
* @since 2.34
*/
public static final String DUPLICATE_METHOD = ISSUE_CODE_PREFIX + "duplicate_method";

private IssueCodes() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import static org.eclipse.xtext.util.Strings.*;
import static org.eclipse.xtext.xbase.validation.IssueCodes.*;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -21,22 +23,38 @@
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.common.types.JvmAnnotationType;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmExecutable;
import org.eclipse.xtext.common.types.JvmField;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmParameterizedTypeReference;
import org.eclipse.xtext.common.types.JvmSpecializedTypeReference;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.JvmVoid;
import org.eclipse.xtext.common.types.JvmWildcardTypeReference;
import org.eclipse.xtext.util.JavaVersion;
import org.eclipse.xtext.validation.AbstractDeclarativeValidator;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.EValidatorRegistrar;
import org.eclipse.xtext.xbase.compiler.GeneratorConfig;
import org.eclipse.xtext.xbase.compiler.IGeneratorConfigProvider;
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedConstructor;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedExecutable;
import org.eclipse.xtext.xbase.typesystem.override.IResolvedOperation;
import org.eclipse.xtext.xbase.typesystem.override.OverrideHelper;
import org.eclipse.xtext.xbase.typesystem.override.ResolvedFeatures;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;

import com.google.common.base.Function;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
Expand All @@ -47,6 +65,12 @@ public class JvmGenericTypeValidator extends AbstractDeclarativeValidator {
@Inject
private IJvmModelAssociations associations;

@Inject
private IGeneratorConfigProvider generatorConfigProvider;

@Inject
private OverrideHelper overrideHelper;

@Override
public void register(EValidatorRegistrar registrar) {
// library validator is not registered for a specific language
Expand Down Expand Up @@ -79,6 +103,7 @@ protected void checkJvmGenericType(JvmGenericType type) {
handleExceptionDuringValidation(() -> checkField(field));
});
handleExceptionDuringValidation(() -> checkMemberNamesAreUnique(type));
handleExceptionDuringValidation(() -> checkDuplicateAndOverriddenFunctions(type));
var members = type.getMembers();
handleExceptionDuringValidation(() -> checkJvmExecutables(members));
handleExceptionDuringValidation(() -> checkJvmGenericTypes(members));
Expand Down Expand Up @@ -188,6 +213,15 @@ protected void checkMemberNamesAreUnique(JvmGenericType type) {
}
}

protected void checkDuplicateAndOverriddenFunctions(JvmGenericType type) {
var sourceType = associations.getPrimarySourceElement(type);
JavaVersion targetVersion = getGeneratorConfig(sourceType).getJavaSourceVersion();
ResolvedFeatures resolvedFeatures = overrideHelper.getResolvedFeatures(type, targetVersion);

Set<EObject> flaggedOperations = Sets.newHashSet();
doCheckDuplicateExecutables(type, resolvedFeatures, flaggedOperations);
}

private <T1 extends JvmMember, T2 extends T1> Map<String, List<T2>> groupMembersByName(
List<T1> members,
Class<T2> memberType) {
Expand Down Expand Up @@ -301,12 +335,100 @@ else if (typeRef instanceof JvmParameterizedTypeReference) {
}

/**
* The default implementation returns the feature "name".
* The default implementation returns the feature "name" or null if it does not exist.
*/
protected EStructuralFeature getFeatureForIssue(EObject object) {
return object.eClass().getEStructuralFeature("name");
}

protected void doCheckDuplicateExecutables(JvmGenericType inferredType, final ResolvedFeatures resolvedFeatures, Set<EObject> flaggedOperations) {
List<IResolvedOperation> declaredOperations = resolvedFeatures.getDeclaredOperations();
doCheckDuplicateExecutables(inferredType, declaredOperations, new Function<String, List<IResolvedOperation>>() {
@Override
public List<IResolvedOperation> apply(String erasedSignature) {
return resolvedFeatures.getDeclaredOperations(erasedSignature);
}
}, flaggedOperations);
final List<IResolvedConstructor> declaredConstructors = resolvedFeatures.getDeclaredConstructors();
doCheckDuplicateExecutables(inferredType, declaredConstructors, new Function<String, List<IResolvedConstructor>>() {
@Override
public List<IResolvedConstructor> apply(String erasedSignature) {
if (declaredConstructors.size() == 1) {
if (erasedSignature.equals(declaredConstructors.get(0).getResolvedErasureSignature())) {
return declaredConstructors;
}
return Collections.emptyList();
}
List<IResolvedConstructor> result = Lists.newArrayListWithCapacity(declaredConstructors.size());
for(IResolvedConstructor constructor: declaredConstructors) {
if (erasedSignature.equals(constructor.getResolvedErasureSignature())) {
result.add(constructor);
}
}
return result;
}
}, flaggedOperations);
}

protected <Executable extends IResolvedExecutable> void doCheckDuplicateExecutables(JvmGenericType inferredType,
List<Executable> declaredOperations, Function<String, List<Executable>> bySignature, Set<EObject> flaggedOperations) {
Set<Executable> processed = Sets.newHashSet();
for(Executable declaredExecutable: declaredOperations) {
if (!processed.contains(declaredExecutable)) {
List<Executable> sameErasure = bySignature.apply(declaredExecutable.getResolvedErasureSignature());
if (sameErasure.size() > 1) {
Multimap<String, Executable> perSignature = HashMultimap.create(sameErasure.size(), 2);
outer: for(Executable executable: sameErasure) {
for(LightweightTypeReference parameterType: executable.getResolvedParameterTypes()) {
if (parameterType.isUnknown())
continue outer;
}
perSignature.put(executable.getResolvedSignature(), executable);
}
if (perSignature.size() > 1) {
for(Collection<Executable> sameSignature: perSignature.asMap().values()) {
for(Executable operationWithSameSignature: sameSignature) {
JvmExecutable executable = operationWithSameSignature.getDeclaration();
EObject otherSource = associations.getPrimarySourceElement(executable);
if (flaggedOperations.add(otherSource)) {
if (sameSignature.size() > 1) {
error("Duplicate " + typeLabel(executable) + " " + operationWithSameSignature.getSimpleSignature()
+ " in type " + inferredType.getSimpleName(), otherSource,
getFeatureForIssue(otherSource), DUPLICATE_METHOD);
} else {
error("The " + typeLabel(executable) + " " + operationWithSameSignature.getSimpleSignature()
+ " has the same erasure "
+ operationWithSameSignature.getResolvedErasureSignature()
+ " as another " + typeLabel(executable) + " in type " + inferredType.getSimpleName(), otherSource,
getFeatureForIssue(otherSource), DUPLICATE_METHOD);
}
}
}
}
}
}
}
}
}

protected String typeLabel(JvmExecutable executable) {
if (executable instanceof JvmOperation)
return "method";
else if(executable instanceof JvmConstructor)
return "constructor";
else
return "?";
}

protected GeneratorConfig getGeneratorConfig(EObject element) {
GeneratorConfig result = (GeneratorConfig) getContext().get(GeneratorConfig.class);
if (result == null) {
result = generatorConfigProvider.get(element);
getContext().put(GeneratorConfig.class, result);
}
return result;
}

/**
* When the {@link AbstractDeclarativeValidator} executes {@link Check} methods,
* it swallows some runtime exceptions so that other methods can be executed.
Expand Down

0 comments on commit ec5270f

Please sign in to comment.