use of org.eclipse.n4js.ts.types.TMember in project n4js by eclipse.
the class ScriptApiTracker method _computeMissingApiGetterSetter.
/**
* Internal algorithm.
*/
private List<AccessorTuple> _computeMissingApiGetterSetter(TN4Classifier declaration, List<AccessorTuple> concreteAccessorTuples, Predicate<ProjectComparisonEntry> filterPredicate, boolean recursive) {
Optional<ProjectComparisonAdapter> optAdapt = firstProjectComparisonAdapter(declaration.eResource());
if (optAdapt.isPresent()) {
ProjectComparisonAdapter projectComparisonAdapter = optAdapt.get();
ProjectComparisonEntry compareEntry = projectComparisonAdapter.getEntryFor(EcoreUtil2.getContainerOfType(declaration, TModule.class));
ProjectComparisonEntry typeCompare = compareEntry.getChildForElementImpl(declaration);
if (typeCompare == null) {
return Collections.emptyList();
}
Predicate<ProjectComparisonEntry> filter = (pce -> (pce.getElementAPI() instanceof TGetter) || (pce.getElementAPI() instanceof TSetter));
filter = filter.and(pce -> pce.getElementImpl()[0] == null).and(filterPredicate);
ArrayList<ProjectComparisonEntry> collectedPCEofGetterOrSetter = new ArrayList<>();
Function<TN4Classifier, Consumer<? super ProjectComparisonEntry>> actionProvider = pivot -> pce -> {
// Get or Set ??
collectedPCEofGetterOrSetter.add(pce);
};
// recursive Extension will generate a stream of compareEntries.
if (recursive)
interfaceApiSupertypeWalker(filter, actionProvider, projectComparisonAdapter, (TN4Classifier) typeCompare.getElementAPI(), TN4Classifier.class);
// ----
/*-
Cases of the Implementation: A getter or setter can
- be given as AST (x)
- be missing (m)
- were not required by API (/)
So we have 3*3=9 cases:
get set
(x) (x) --> all fine, pair will be transpiled
(x) (m) --> code for getter will be transpiled, need to inject virtual setter code into existing tuple.
(x) (/) --> all fine, getter will be transpiled
(m) (x) --> code for setter will be transpiled, need to inject virtual getter code into existing tuple.
(m) (m) --> need to create virtual accessor tuple (similar to missing field) with setter & getter
(m) (/) --> need to create virtual accessor tuple with getter only
(/) (x) --> all fine
(/) (m) --> need to create virtual accessor tuple with setter only
(/) (/) --> all fine nothing to be done.
*/
List<ProjectComparisonEntry> getSetList;
if (recursive)
getSetList = collectedPCEofGetterOrSetter;
else
getSetList = typeCompare.allChildren().filter(pce -> (pce.getElementAPI() instanceof TGetter) || (pce.getElementAPI() instanceof TSetter)).filter(filterPredicate).collect(Collectors.toList());
HashMap<Pair<String, Boolean>, GetSetGroup> hmName2getset = new HashMap<>();
for (ProjectComparisonEntry pce : getSetList) {
TMember apiAsMember = ((TMember) pce.getElementAPI());
String name = apiAsMember.getName();
boolean staticCase = apiAsMember.isStatic();
Pair<String, Boolean> key = Pair.of(name, staticCase);
GetSetGroup group = hmName2getset.get(key);
if (group == null) {
group = new GetSetGroup(name, staticCase);
hmName2getset.put(key, group);
}
if (pce.getElementAPI() instanceof TGetter) {
// case getter:
TGetter apiGetter = (TGetter) pce.getElementAPI();
if (pce.getElementImpl(0) != null) {
// case (x) for getter-
group.getterIsInAST = true;
} else {
// case (m) for getter-
group.getterIsInAST = false;
group.getter = new VirtualApiTGetter(name, apiGetter);
}
} else if (pce.getElementAPI() instanceof TSetter) {
// case setter:
TSetter apiSetter = (TSetter) pce.getElementAPI();
if (pce.getElementImpl(0) != null) {
// case (x) for setter -
group.setterIsInAST = true;
} else {
// case (m) for setter:
group.setterIsInAST = false;
group.setter = new VirtualApiTSetter(name, apiSetter);
}
}
}
// go over the list of known AccessorTupels and enhance them by adding virtual things.
for (AccessorTuple conAccTupel : concreteAccessorTuples) {
GetSetGroup getset = hmName2getset.remove(Pair.of(conAccTupel.getName(), conAccTupel.isStatic()));
if (getset != null) {
// some missings found:
if (getset.hasGetter() && !getset.getterIsInAST && // could be mixed in by interface-default-impl different
conAccTupel.getGetter() == null) // to the intended API-path c.f. GHOLD-212
{
conAccTupel.setGetter(getset.getter);
}
if (getset.hasSetter() && !getset.setterIsInAST && // could be mixed in by interface-default-impl different
conAccTupel.getSetter() == null) // to the intended API-path c.f. GHOLD-212
{
conAccTupel.setSetter(getset.setter);
}
}
}
// remaining entries in hmName2getset need to translated into VirtualApiAccessors.
List<AccessorTuple> ret = new ArrayList<>();
for (GetSetGroup getset : hmName2getset.values()) {
VirtualApiAccessorTuple vAccessTupel = new VirtualApiAccessorTuple(getset.name, getset.staticCases);
if (getset.getter != null)
vAccessTupel.setGetter(getset.getter);
if (getset.setter != null)
vAccessTupel.setSetter(getset.setter);
ret.add(vAccessTupel);
}
return ret;
}
return emptyList();
}
use of org.eclipse.n4js.ts.types.TMember in project n4js by eclipse.
the class ScriptApiTracker method computeMethodDiff.
/**
* Find last missing methods, which the implType would have if it would follow the inheritance as defined in the API
*
* @param implType
* Type of implementation project, calculated from AST
* @param collector
* member collector for the project.
* @param ownedAndMixedInConcreteMember
* already computed for implType
* @param missingApiMethods2
* already computed for implType
* @return list of virtual Methods
*/
public List<VirtualApiTMethod> computeMethodDiff(TClass implType, MemberCollector collector, List<TMember> ownedAndMixedInConcreteMember, MemberList<TMethod> missingApiMethods2) {
Optional<ProjectComparisonAdapter> optAdapt = firstProjectComparisonAdapter(implType.eResource());
if (optAdapt.isPresent()) {
ProjectComparisonEntry compareEntry = optAdapt.get().getEntryFor(EcoreUtil2.getContainerOfType(implType, TModule.class));
ProjectComparisonEntry typeCompare = compareEntry.getChildForElementImpl(implType);
if (typeCompare != null && typeCompare.getElementAPI() != null) {
TClass apiType = (TClass) typeCompare.getElementAPI();
MemberList<TMember> implMembers = collector.allMembers(implType, false, true);
MemberList<TMember> apiMembers = collector.allMembers(apiType, false, true);
final HashSet<String> methodNamesAlreadyDefined = new HashSet<>();
Stream.concat(implMembers.stream(), Stream.concat(ownedAndMixedInConcreteMember.stream(), missingApiMethods2.stream())).forEach(m -> {
if (m instanceof TMethod) {
methodNamesAlreadyDefined.add(m.getName());
}
});
return apiMembers.stream().filter(t -> t instanceof TMethod).filter(m -> !methodNamesAlreadyDefined.contains(((TMethod) m).getName())).map(m2 -> {
TMethod m = (TMethod) m2;
VirtualApiTMethod vMethod = new VirtualApiTMethod(m.getName(), TypeUtils.copyPartial(m, TypesPackage.Literals.SYNTAX_RELATED_TELEMENT__AST_ELEMENT));
return vMethod;
}).collect(Collectors.toList());
}
}
return emptyList();
}
use of org.eclipse.n4js.ts.types.TMember in project n4js by eclipse.
the class ProjectCompareHelper method internalCompareApiImpl.
private ProjectCompareResult internalCompareApiImpl(ProjectComparisonEntry entry, int implIdx) {
if (!entry.isElementEntry())
// not an entry for an EObject element -> never report differences
return ProjectCompareResult.equal();
final int implCount = entry.getImplCount();
if (implIdx < 0 || implIdx >= implCount)
// implementation index out of range -> never report differences
return ProjectCompareResult.equal();
// compare implementation to API
final EObject api = entry.getElementAPI();
final EObject impl = entry.getElementImpl()[implIdx];
// special case: no API
if (api == null) {
if (impl != null)
return ProjectCompareResult.compliant();
else
return ProjectCompareResult.equal();
}
// special case: no impl
if (impl == null) {
// note: we know api!=null because of above check
return ProjectCompareResult.error("missing implementation");
}
// accessibility-based compare:
if (api instanceof TMember && impl instanceof TMember) {
// order important: check for member before type!
if (AccessModifiers.less((TMember) impl, (TMember) api))
return ProjectCompareResult.error("reduced visibility");
} else if (api instanceof Type && impl instanceof Type) {
final MemberAccessModifier apiAcc = AccessModifiers.toMemberModifier((Type) api);
final MemberAccessModifier implAcc = AccessModifiers.toMemberModifier((Type) impl);
if (AccessModifiers.less(implAcc, apiAcc))
return ProjectCompareResult.error("reduced visibility");
}
ImplToApiReplacementProvider typeReplacementProvider = new ImplToApiReplacementProvider(entry.getRoot());
// subtype-based compare:
if (api instanceof TMember && impl instanceof TMember) {
final TMember apiMember = (TMember) api;
final TMember implMember = (TMember) impl;
if (apiMember instanceof TField) {
boolean bAPIProvidesInitializer = PROVIDES_INITIALZER.hasAnnotation(apiMember);
if (bAPIProvidesInitializer && !hasInitializer(impl)) {
if (bAPIProvidesInitializer) {
return ProjectCompareResult.error("no initializer in implementation but @" + PROVIDES_INITIALZER.name + " in API");
} else {
return ProjectCompareResult.error("initializer in implementation but no @" + PROVIDES_INITIALZER.name + " in API)");
}
}
} else {
// Method or accessor
boolean bAPIProvidesDefImpl = PROVIDES_DEFAULT_IMPLEMENTATION.hasAnnotation(apiMember);
if ((bAPIProvidesDefImpl != hasBody(impl)) && apiMember.eContainer() instanceof TInterface) {
if (bAPIProvidesDefImpl) {
return ProjectCompareResult.error("no body in implementation but @" + PROVIDES_DEFAULT_IMPLEMENTATION.name + " in API");
} else {
return ProjectCompareResult.error("body in implementation but no @" + PROVIDES_DEFAULT_IMPLEMENTATION.name + " in API");
}
}
}
final TypeRef context = TypeUtils.createTypeRef((Type) api.eContainer());
final TypeRef typeApi = typeSystem.tau(apiMember, context);
final TypeRef typeImpl = typeSystem.tau(implMember, context);
final RuleEnvironment G = RuleEnvironmentExtensions.newRuleEnvironment(api);
RuleEnvironmentExtensions.setTypeReplacement(G, typeReplacementProvider);
final Result<Boolean> implSubtypeApi = typeSystem.subtype(G, typeImpl, typeApi);
final Result<Boolean> apiSubtypeImpl = typeSystem.subtype(G, typeApi, typeImpl);
final boolean isImplSubtypeApi = !implSubtypeApi.failed();
final boolean isApiSubtypeImpl = !apiSubtypeImpl.failed();
final boolean isEqualType = isImplSubtypeApi && isApiSubtypeImpl;
if (!isEqualType) {
if (isImplSubtypeApi)
// not equal but at least compliant
return ProjectCompareResult.compliant();
else {
final String msg = implSubtypeApi.getRuleFailedException().getLocalizedMessage();
// not even compliant
return ProjectCompareResult.error(msg);
}
}
if (isSpecialCaseOfHiddenMethodDiff(api, impl)) {
// not equal but at least compliant
return ProjectCompareResult.compliant();
}
// all fine
return ProjectCompareResult.equal();
}
// classifier compare
if (api instanceof TClassifier && impl instanceof TClassifier) {
TClassifier apiClassifier = (TClassifier) api;
TClassifier implClassifier = (TClassifier) impl;
EList<TypeVariable> apiTypeVars = apiClassifier.getTypeVars();
EList<TypeVariable> implTypeVars = implClassifier.getTypeVars();
// check for number of type variables
if (apiTypeVars.size() != implTypeVars.size()) {
return ProjectCompareResult.error("the number of type variables doesn't match");
}
final RuleEnvironment ruleEnvironment = RuleEnvironmentExtensions.newRuleEnvironment(api);
RuleEnvironmentExtensions.setTypeReplacement(ruleEnvironment, typeReplacementProvider);
// check for upper bound compatibility
for (int i = 0; i < apiTypeVars.size(); i++) {
TypeVariable apiTypeVar = apiTypeVars.get(i);
TypeVariable implTypeVar = implTypeVars.get(i);
TypeRef apiDeclaredUpperBound = apiTypeVar.getDeclaredUpperBound();
TypeRef implDeclaredUpperBound = implTypeVar.getDeclaredUpperBound();
if ((apiDeclaredUpperBound != null) != (implDeclaredUpperBound != null) || (apiDeclaredUpperBound != null && implDeclaredUpperBound != null && !typeSystem.equaltypeSucceeded(ruleEnvironment, apiDeclaredUpperBound, implDeclaredUpperBound))) {
return ProjectCompareResult.error(String.format("the upper bound of type variable %s isn't compatible with the API", implTypeVar.getName()));
}
}
return ProjectCompareResult.equal();
}
// text-based compare:
// always compare with API
final String textApi = entry.getTextAPI();
final String textImpl = entry.getTextImpl(implIdx);
final boolean isEqual = textApi != null ? textApi.equals(textImpl) : textImpl == null;
if (!isEqual)
return ProjectCompareResult.error(textImpl + " is not equal to " + textApi);
return ProjectCompareResult.equal();
}
use of org.eclipse.n4js.ts.types.TMember in project n4js by eclipse.
the class ScopeXpectMethod method binding.
/**
* Checks that a given element is bound to something identified by (simple) qualified name. The check is designed as
* simple as possible. That is, simply the next following expression is tested, and within that we expect a property
* access or a direct identifiable element. The compared name is the simple qualified name, that is container (type)
* followed by elements name, without URIs of modules etc.
*/
@Xpect
@ParameterParser(syntax = "('at' arg1=OFFSET)?")
public //
void binding(//
@CommaSeparatedValuesExpectation ICommaSeparatedValuesExpectation expectation, //
ICrossEReferenceAndEObject arg1) {
EObject eobj = arg1.getEObject();
ParameterizedPropertyAccessExpression ppae = EcoreUtil2.getContainerOfType(eobj, ParameterizedPropertyAccessExpression.class);
IdentifiableElement element;
if (ppae != null) {
element = ppae.getProperty();
} else if (eobj instanceof IdentifiableElement) {
element = (IdentifiableElement) eobj;
} else {
throw new IllegalArgumentException("Cannot check binding for " + (eobj == null ? "null" : eobj.eClass().getName()));
}
String container = "";
if (element instanceof TMember) {
container = ((TMember) element).getContainingType().getName() + ".";
}
final String qn = container + element.getName();
// URI uri = eobj == null ? null : eobj.eResource() == null ? null : eobj.eResource().getURI();
expectation.assertEquals(Collections.singleton(qn));
}
use of org.eclipse.n4js.ts.types.TMember in project n4js by eclipse.
the class MemberByNameAndAccessMap method get.
@Override
public TMember get(Object key) {
if (key instanceof NameAndAccess) {
for (int i = 0, size = members.size(); i < size; i++) {
final TMember result = members.get(i);
final NameAndAccess nameAndAccess = (NameAndAccess) key;
if (nameAndAccess.getName().equals(result.getName())) {
if (nameAndAccess.isStaticAccess() == result.isStatic()) {
if ((nameAndAccess.isWriteAccess() && result.isWriteable()) || (!nameAndAccess.isWriteAccess() && result.isReadable())) {
return result;
}
}
}
}
}
return null;
}
Aggregations