/******************************************************************************* * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ package org.eclipse.xtext.xbase.typesystem.internal; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.common.types.JvmExecutable; import org.eclipse.xtext.common.types.JvmFormalParameter; import org.eclipse.xtext.common.types.JvmGenericType; import org.eclipse.xtext.common.types.JvmIdentifiableElement; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.JvmType; import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.impl.AbstractScope; import org.eclipse.xtext.xbase.XAbstractFeatureCall; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XMemberFeatureCall; import org.eclipse.xtext.xbase.scoping.batch.BucketedEObjectDescription; import org.eclipse.xtext.xbase.scoping.batch.FeatureScopes; import org.eclipse.xtext.xbase.scoping.batch.IFeatureNames; import org.eclipse.xtext.xbase.scoping.batch.IFeatureScopeSession; import org.eclipse.xtext.xbase.scoping.batch.IIdentifiableElementDescription; import org.eclipse.xtext.xbase.typesystem.IExpressionScope; import org.eclipse.xtext.xbase.typesystem.IResolvedTypes; import org.eclipse.xtext.xbase.typesystem.references.ITypeReferenceOwner; import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference; import org.eclipse.xtext.xbase.typesystem.util.Maps2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * @author Sebastian Zarnekow - Initial contribution and API */ public class ExpressionScope implements IExpressionScope { private final FeatureScopes featureScopes; private final EObject context; private final List<FeatureScopeSessionToResolvedTypes> data; private final Anchor anchor; private EnumMap<Anchor, IScope> cachedFeatureScope = Maps.newEnumMap(Anchor.class); private IScope cachedReceiverFeatureScope; private XAbstractFeatureCall requestedFeatureCall; private ITypeReferenceOwner owner; public ExpressionScope(FeatureScopes featureScopes, EObject context, Anchor anchor, ITypeReferenceOwner owner) { this.owner = owner; this.data = Lists.newArrayListWithExpectedSize(2); this.featureScopes = featureScopes; this.context = context; this.anchor = anchor; } public IExpressionScope withAnchor(final Anchor anchor) { if (anchor == this.anchor) return this; return new IExpressionScope() { /* @NonNull */ @Override public IScope getFeatureScope() { return ExpressionScope.this.getFeatureScope(anchor); } /* @NonNull */ @Override public IScope getFeatureScope(/* @Nullable */ XAbstractFeatureCall currentFeatureCall) { return ExpressionScope.this.getFeatureScope(currentFeatureCall, anchor); } /* @NonNull */ @Override public List<String> getTypeNamePrefix() { return ExpressionScope.this.getTypeNamePrefix(); } @Override public boolean isPotentialTypeLiteral() { return ExpressionScope.this.isPotentialTypeLiteral(); } }; } /* @NonNull */ protected IScope getFeatureScope(Anchor anchor) { IScope cached = cachedFeatureScope.get(anchor); if (cached != null) return cached; if (anchor != Anchor.RECEIVER) { cached = createSimpleFeatureCallScope(); cachedFeatureScope.put(anchor, cached); return cached; } else if (context instanceof XExpression) { cached = createFeatureCallScopeForReceiver(null); // receiver is missing intentionally cachedFeatureScope.put(anchor, cached); return cached; } cachedFeatureScope.put(anchor, IScope.NULLSCOPE); return IScope.NULLSCOPE; } protected IScope createFeatureCallScopeForReceiver(XAbstractFeatureCall receiver) { List<FeatureScopeSessionToResolvedTypes> dataToUse; if (data.size() == 1) dataToUse = data; else { dataToUse = Lists.newArrayListWithExpectedSize(3); Set<String> seenReceiverTypes = Sets.newHashSet(); for(int i = 0; i < data.size(); i++) { FeatureScopeSessionToResolvedTypes next = data.get(i); LightweightTypeReference receiverType = next.getTypes().getActualType(receiver); if (receiverType == null) { if (seenReceiverTypes.add(null)) { dataToUse.add(next); } } else if (seenReceiverTypes.add(receiverType.getIdentifier())) { dataToUse.add(next); } } } if (dataToUse.size() == 1) { FeatureScopeSessionToResolvedTypes single = dataToUse.get(0); IScope result = new Scope(featureScopes.createFeatureCallScopeForReceiver(receiver, (XExpression) context, single.getSession(), single.getTypes()), owner); return result; } else { IScope result = IScope.NULLSCOPE; for(int i = dataToUse.size() - 1; i >= 0; i--) { FeatureScopeSessionToResolvedTypes f = dataToUse.get(i); result = new DelegateScope(result, featureScopes.createFeatureCallScopeForReceiver(receiver, (XExpression) context, f.getSession(), f.getTypes())); } return new Scope(result, owner); } } protected IScope createSimpleFeatureCallScope() { List<FeatureScopeSessionToResolvedTypes> dataToUse; if (data.size() == 1) dataToUse = data; else { dataToUse = Lists.newArrayListWithExpectedSize(3); Set<FeatureScopeSessionToResolvedTypes> seenSessionData = Sets.newHashSet(); for(int i = 0; i < data.size(); i++) { FeatureScopeSessionToResolvedTypes next = data.get(i); if (seenSessionData.add(next)) dataToUse.add(next); } } if (dataToUse.size() == 1) { FeatureScopeSessionToResolvedTypes single = dataToUse.get(0); IScope result = new Scope(featureScopes.createSimpleFeatureCallScope(context, single.getSession(), single.getTypes()), owner); return result; } else { IScope result = IScope.NULLSCOPE; for(int i = dataToUse.size() - 1; i >= 0; i--) { FeatureScopeSessionToResolvedTypes f = dataToUse.get(i); result = new DelegateScope(result, featureScopes.createSimpleFeatureCallScope(context, f.getSession(), f.getTypes())); } return new Scope(result, owner); } } /* @NonNull */ public IScope getFeatureScope(/* @Nullable */ XAbstractFeatureCall currentFeatureCall, Anchor anchor) { if (anchor == Anchor.RECEIVER) { if (currentFeatureCall == requestedFeatureCall && cachedReceiverFeatureScope != null) { return cachedReceiverFeatureScope; } IScope result = createFeatureCallScopeForReceiver(currentFeatureCall); this.requestedFeatureCall = currentFeatureCall; return cachedReceiverFeatureScope = result; } return getFeatureScope(anchor); } /* @NonNull */ @Override public IScope getFeatureScope() { return getFeatureScope(this.anchor); } /* @NonNull */ @Override public IScope getFeatureScope(/* @Nullable */ XAbstractFeatureCall currentFeatureCall) { return getFeatureScope(currentFeatureCall, this.anchor); } public void addData(IFeatureScopeSession session, IResolvedTypes types) { this.cachedFeatureScope.clear(); this.cachedReceiverFeatureScope = null; this.requestedFeatureCall = null; this.data.add(new FeatureScopeSessionToResolvedTypes(session, types)); } public void replacePreviousData(IFeatureScopeSession session) { FeatureScopeSessionToResolvedTypes prev = data.remove(data.size() - 1); data.add(new FeatureScopeSessionToResolvedTypes(session, prev.getTypes())); } /* @NonNull */ @Override public List<String> getTypeNamePrefix() { return Collections.emptyList(); } @Override public boolean isPotentialTypeLiteral() { return false; } public static class DelegateScope extends AbstractScope { private IScope delegate; private Set<String> containedKeys; private List<IEObjectDescription> containedElements; protected DelegateScope(IScope parent, IScope delegate) { super(parent, false); this.delegate = delegate; } @Override protected Iterable<IEObjectDescription> getAllLocalElements() { if (containedElements == null) { if (getParent() != IScope.NULLSCOPE) { Iterable<IEObjectDescription> result = delegate.getAllElements(); List<IEObjectDescription> list = Lists.newArrayList(result); Set<String> keys = Sets.newHashSet(); for(IEObjectDescription desc: result) { list.add(desc); keys.add(getShadowingKey(desc)); } containedKeys = keys; containedElements = list; return list; } else { return delegate.getAllElements(); } } return containedElements; } @Override protected boolean isShadowed(IEObjectDescription fromParent) { if (containedKeys == null) { return super.isShadowed(fromParent); } return containedKeys.contains(getShadowingKey(fromParent)); } protected String getShadowingKey(IEObjectDescription description) { if (description instanceof BucketedEObjectDescription) { return ((BucketedEObjectDescription) description).getShadowingKey(); } return description.getName().toString(); } } public static class Scope implements IScope { private final IScope delegate; private final ITypeReferenceOwner owner; private List<IEObjectDescription> allElements; private Map<QualifiedName, List<IEObjectDescription>> allElementsByName; public Scope(IScope delegate, ITypeReferenceOwner owner) { this.delegate = delegate; this.owner = owner; } @Override public IEObjectDescription getSingleElement(QualifiedName name) { return delegate.getSingleElement(name); } @Override public Iterable<IEObjectDescription> getElements(QualifiedName name) { ensureInitialized(); List<IEObjectDescription> result = allElementsByName.get(name); if (result != null) { return result; } return Collections.emptyList(); } @Override public IEObjectDescription getSingleElement(EObject object) { return delegate.getSingleElement(object); } @Override public Iterable<IEObjectDescription> getElements(EObject object) { return delegate.getElements(object); } @Override public Iterable<IEObjectDescription> getAllElements() { ensureInitialized(); return allElements; } protected void ensureInitialized() { if (allElements == null) { List<IEObjectDescription> allElements = Lists.newArrayList(); Map<QualifiedName, List<IEObjectDescription>> allElementsByName = Maps.newHashMap(); populateFromParent(allElements, allElementsByName); this.allElements = allElements; this.allElementsByName = allElementsByName; } } protected void populateFromParent(List<IEObjectDescription> allElements, Map<QualifiedName, List<IEObjectDescription>> allElementsByName) { Map<String, List<IIdentifiableElementDescription>> extensionSignatures = Maps.newHashMap(); Map<String, IEObjectDescription> signatures = Maps.newHashMap(); for(IEObjectDescription element: delegate.getAllElements()) { if (element instanceof IIdentifiableElementDescription) { IIdentifiableElementDescription desc = (IIdentifiableElementDescription) element; if (!desc.isVisible() || !desc.isValidStaticState() || isInvalidThisReference(desc)) { continue; } if (desc.isExtension()) { // filter extensions by most specific first parameter Maps2.putIntoListMap(getExtensionSignature(desc), desc, extensionSignatures); } else { if (desc.getImplicitReceiver() != null) { if (desc.getSyntacticReceiver() instanceof XMemberFeatureCall) { continue; } } recordDescription(desc, signatures); } } else { recordDescription(element, signatures); } } List<IIdentifiableElementDescription> extensionDescriptions = getFilteredExtensionDescriptions(extensionSignatures); for(IIdentifiableElementDescription desc: extensionDescriptions) { recordDescription(desc, signatures); } for(IEObjectDescription valid: signatures.values()) { allElements.add(valid); Maps2.putIntoListMap(valid.getName(), valid, allElementsByName); } } private boolean isInvalidThisReference(IIdentifiableElementDescription desc) { EObject object = desc.getEObjectOrProxy(); return object instanceof JvmGenericType && ((JvmGenericType) object).isInterface() && IFeatureNames.THIS.equals(desc.getName()); } protected void recordDescription(IEObjectDescription element, Map<String, IEObjectDescription> result) { recordDescription(getSignature(element), element, result); } protected void recordDescription(String signature, IEObjectDescription element, Map<String, IEObjectDescription> result) { IEObjectDescription known = result.get(signature); if (known != null) { if (!isAliased(known)) { return; } if (isAliased(element)) { return; } } result.put(signature, element); } protected boolean isAliased(IEObjectDescription desc) { String actualName = ((JvmIdentifiableElement) desc.getEObjectOrProxy()).getSimpleName(); String usedName = desc.getName().getFirstSegment(); if (actualName.equals(usedName)) { return false; } return true; } protected void recordDescription(IIdentifiableElementDescription desc, Map<String, IEObjectDescription> result) { recordDescription(getSignature(desc), desc, result); } protected LightweightTypeReference getFirstParameterType(IIdentifiableElementDescription candidate) { JvmOperation operation = (JvmOperation) candidate.getElementOrProxy(); if (operation.getParameters().isEmpty()) { return null; } return getParameterType(operation.getParameters().get(0)); } protected LightweightTypeReference getParameterType(JvmFormalParameter p) { JvmTypeReference parameterType = p.getParameterType(); if (parameterType == null) { return null; } JvmType type = parameterType.getType(); if (type == null) return null; return owner.toPlainTypeReference(type).getRawTypeReference(); } /** * Filters the extensions by their most specific first parameter. */ protected List<IIdentifiableElementDescription> getFilteredExtensionDescriptions(Map<String, List<IIdentifiableElementDescription>> extensionSignatures) { List<IIdentifiableElementDescription> result = Lists.newArrayList(); for(List<IIdentifiableElementDescription> list: extensionSignatures.values()) { int size = list.size(); for(int i = 0; i < size; i++) { IIdentifiableElementDescription candidate = list.get(i); if (candidate != null) { LightweightTypeReference firstParameterType = getFirstParameterType(candidate); if (firstParameterType != null) { if (i + 1 < list.size()) { for(int j = i + 1; j < list.size() && firstParameterType != null; j++) { IIdentifiableElementDescription next = list.get(j); if (next != null) { if (next.isStatic() != candidate.isStatic()) { if (next.isStatic()) { list.set(j, null); } else { list.set(j, null); candidate = next; firstParameterType = getFirstParameterType(next); } } else { LightweightTypeReference otherFirstParameterType = getFirstParameterType(next); if (otherFirstParameterType != null) { if (otherFirstParameterType.isAssignableFrom(firstParameterType)) { list.set(j, null); } else if (firstParameterType.isAssignableFrom(otherFirstParameterType)) { list.set(j, null); candidate = next; firstParameterType = otherFirstParameterType; } } } } } } } result.add(candidate); } } } return result; } protected String getExtensionSignature(IIdentifiableElementDescription desc) { JvmOperation operation = (JvmOperation) desc.getElementOrProxy(); StringBuilder builder = new StringBuilder(64).append(desc.getName()); String opName = operation.getSimpleName(); if (opName.length() - 3 == desc.getName().getFirstSegment().length() && opName.startsWith("set")) { builder.append("="); } appendParameters(operation, builder, desc.isExtension()); return builder.toString(); } protected String getSignature(IEObjectDescription desc) { if (desc instanceof IIdentifiableElementDescription) { return getSignature((IIdentifiableElementDescription) desc); } return desc.getName().toString(); } protected String getSignature(IIdentifiableElementDescription desc) { String descName = desc.getName().getFirstSegment(); StringBuilder builder = new StringBuilder(64).append(descName); JvmIdentifiableElement elementOrProxy = desc.getElementOrProxy(); if (elementOrProxy instanceof JvmExecutable) { JvmExecutable executable = (JvmExecutable) desc.getElementOrProxy(); String opName = executable.getSimpleName(); if (opName.length() - 3 == descName.length() && opName.startsWith("set")) { builder.append("="); } appendParameters(executable, builder, desc.isExtension()); } return builder.toString(); } protected void appendParameters(JvmExecutable executable, StringBuilder result, boolean extension) { List<JvmFormalParameter> parameters = executable.getParameters(); int start = extension ? 1 : 0; int end = parameters.size(); if (start != end) { result.append('('); for(int i = start; i < end; i++) { if (i != start) { result.append(','); } JvmFormalParameter parameter = parameters.get(i); LightweightTypeReference parameterType = getParameterType(parameter); if (parameterType != null) result.append(parameterType.getIdentifier()); else result.append("[null]"); } result.append(')'); } } } }