blob: ba3dc410815df043cd14961e9cd2bbbe22998007 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Nathan Ridge.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nathan Ridge - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp.semantics;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTUnaryExpression;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.IPointerType;
import org.eclipse.cdt.core.dom.ast.IQualifierType;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPAliasTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateArgument;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPPointerType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownMember;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownMemberClass;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownMemberClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownType;
/**
* The purpose of this class is to perform heuristic binding resolution
* in contexts where the results of ordinary binding resolution (whose
* approach to templates is "defer actual resolution until template
* arguments become available") are undesirable.
*
* Usually, this comes up in cases where the user is trying to invoke
* certain editor functionality inside a template.
*
* For example, consider the following code:
*
* struct Cat {
* void meow();
* };
*
* template <typename T>
* struct B {
* Cat foo();
* };
*
* template <typename T>
* void foo(B<T> a) {
* a.foo().
* }
*
* and suppose content assist is invoked after the "a.foo().".
* To determine what completions to provide in that context, we try
* to determine the type of 'a.foo()', and then look to see what
* members are inside that type.
*
* However, because we're in a template, the type of 'a.foo()' is
* a deferred / unknown type (in this case, a TypeOfDependentExpression),
* so we don't know what members it has.
*
* HeuristicResolver maps that unknown type to a concrete type
* (in this case, 'Cat') by applying the following heuristic:
* whenever name lookup is deferred because the lookup scope is
* the scope of a dependent template instantiation, assume the
* instantiation uses the primary template (as opposed to a partial
* or explicit specialization), and perform the lookup in the
* primary template scope. This heuristic gives the right answer
* in many cases, including this one.
*
* HeuristicResolver can handle some more complex situations as well,
* such as metafunction calls, typedefs, and nested templates. See
* CompletionTests.testDependentScopes_bug472818c for a test case
* that pushes it to its limit.
*
* However, due to the nature of its heuristic, it cannot handle
* cases where the correct answer requires selecting a specialization
* rather than the primary template. Bug 487700 is on file for
* implementing more advanced heuristics that could deal with this.
*/
public class HeuristicResolver {
/**
* Given a dependent type, heuristically tries to find a concrete scope (i.e. not an unknown scope)
* for it.
*/
public static IScope findConcreteScopeForType(IType type) {
if (type instanceof ICPPUnknownType) {
type = resolveUnknownType((ICPPUnknownType) type, SemanticUtil.TDEF | SemanticUtil.REF | SemanticUtil.CVTYPE);
}
type = SemanticUtil.getNestedType(type, SemanticUtil.PTR);
if (type instanceof ICompositeType) {
return ((ICompositeType) type).getCompositeScope();
} else if (type instanceof ICPPEnumeration) {
return ((ICPPEnumeration) type).asScope();
}
return null;
}
/**
* Helper function for lookInside().
* Specializes the given bindings in the given context.
*/
private static IBinding[] specializeBindings(IBinding[] bindings, ICPPClassSpecialization context) {
IBinding[] result = new IBinding[bindings.length];
for (int i = 0; i < bindings.length; ++i) {
result[i] = context.specializeMember(bindings[i]);
}
return result;
}
/**
* An extension of CPPDeferredClassInstance that implements ICPPClassSpecialization,
* allowing its members to be specialized via specializeMember().
*/
private static class CPPDependentClassInstance extends CPPDeferredClassInstance
implements ICPPClassSpecialization {
public CPPDependentClassInstance(ICPPDeferredClassInstance deferredInstance) {
super(chooseTemplateForDeferredInstance(deferredInstance),
deferredInstance.getTemplateArguments());
}
@Override
public ICPPClassType getSpecializedBinding() {
return (ICPPClassType) super.getSpecializedBinding();
}
@Override
@Deprecated
public IBinding specializeMember(IBinding binding, IASTNode point) {
return specializeMember(binding);
}
// This overload of specializeMember() is all we're interested in.
// Everything else is unsupported.
@Override
public IBinding specializeMember(IBinding binding) {
return CPPTemplates.createSpecialization(this, binding);
}
@Override
public ICPPBase[] getBases(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPConstructor[] getConstructors(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPField[] getDeclaredFields(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPMethod[] getMethods(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPMethod[] getAllDeclaredMethods(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPMethod[] getDeclaredMethods(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public IBinding[] getFriends(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public IField[] getFields(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPClassType[] getNestedClasses(IASTNode point) {
throw new UnsupportedOperationException();
}
@Override
public ICPPUsingDeclaration[] getUsingDeclarations(IASTNode point) {
throw new UnsupportedOperationException();
}
}
/**
* Represents a lookup of a name in a primary template scope.
* The set of such lookups during a heuristic resolution operation is
* tracked, to avoid infinite recursion.
*/
private static class HeuristicLookup {
public IScope scope;
public char[] name;
public HeuristicLookup(IScope scope, char[] name) {
this.scope = scope;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof HeuristicLookup)) {
return false;
}
HeuristicLookup otherLookup = (HeuristicLookup) other;
return scope == otherLookup.scope
&& CharArrayUtils.equals(name, otherLookup.name);
}
@Override
public int hashCode() {
return scope.hashCode() * (29 + name.hashCode());
}
}
/**
* Helper function for resolveUnknownType() and resolveUnknownBinding().
* Heuristically resolves the given unknown type and performs name lookup inside it.
*
* If name lookup is performed inside a template scope to approximate lookup
* in the scope of a dependent instantiation, the lookup results are
* specialized in the context of the dependent instantiation.
*
* @param ownerType the type to perform name lookup inside
* @param isPointerDeref true if 'ownerType' is a pointer type
* @param name the name to be looked up
* @param templateArgs template arguments following the name, if any
* @param lookupSet the set of lookups performed so far; lookups during this call are added to this
* @param point point of instantiation for name lookups
* @return results of the name lookup
*/
private static IBinding[] lookInside(IType ownerType, boolean isPointerDeref, char[] name,
ICPPTemplateArgument[] templateArgs, Set<HeuristicLookup> lookupSet) {
// If this is a pointer dereference, the pointer type might be outside of the dependent type.
ownerType = SemanticUtil.getSimplifiedType(ownerType);
if (isPointerDeref && ownerType instanceof IPointerType) {
ownerType = ((IPointerType) ownerType).getType();
isPointerDeref = false;
}
if (ownerType instanceof IQualifierType) {
ownerType = ((IQualifierType) ownerType).getType();
}
IType lookupType = ownerType;
ICPPClassSpecialization specializationContext = null;
if (lookupType instanceof ICPPUnknownType) {
// Here we have a loop similar to the one in resolveUnknownType(), but we stop when
// we get a result that's an ICPPClassSpecialization or an ICPPDeferredClassInstance,
// so we can use it to specialize the lookup results as appropriate.
while (true) {
if (lookupType instanceof ICPPClassSpecialization) {
specializationContext = (ICPPClassSpecialization) lookupType;
lookupType = specializationContext.getSpecializedBinding();
break;
} else if (lookupType instanceof ICPPDeferredClassInstance) {
specializationContext = new CPPDependentClassInstance(
(ICPPDeferredClassInstance) lookupType);
lookupType = specializationContext.getSpecializedBinding();
break;
}
IType resolvedType = resolveUnknownTypeOnce((ICPPUnknownType) lookupType, lookupSet);
resolvedType = SemanticUtil.getNestedType(resolvedType,
SemanticUtil.TDEF | SemanticUtil.REF | SemanticUtil.CVTYPE);
// If this is a pointer dereference, and the pointer type wasn't
// outside the
// dependent type, it might be inside the dependent type.
if (isPointerDeref) {
if (resolvedType instanceof IPointerType) {
isPointerDeref = false;
resolvedType = ((IPointerType) resolvedType).getType();
} else {
resolvedType = null;
}
}
resolvedType = SemanticUtil.getNestedType(resolvedType,
SemanticUtil.CVTYPE | SemanticUtil.TDEF);
if (resolvedType == lookupType || !(resolvedType instanceof ICPPUnknownType)) {
lookupType = resolvedType;
break;
} else {
lookupType = resolvedType;
continue;
}
}
}
IScope lookupScope = null;
if (lookupType instanceof ICPPClassType) {
lookupScope = ((ICPPClassType) lookupType).getCompositeScope();
} else if (lookupType instanceof ICPPEnumeration) {
lookupScope = ((ICPPEnumeration) lookupType).asScope();
}
if (lookupScope != null) {
HeuristicLookup entry = new HeuristicLookup(lookupScope, name);
if (lookupSet.add(entry)) {
LookupData lookup = new LookupData(name, templateArgs, CPPSemantics.getCurrentLookupPoint());
lookup.fHeuristicBaseLookup = true;
try {
CPPSemantics.lookup(lookup, lookupScope);
IBinding[] foundBindings = lookup.getFoundBindings();
if (foundBindings.length > 0) {
if (specializationContext != null) {
foundBindings = specializeBindings(foundBindings, specializationContext);
}
return foundBindings;
}
} catch (DOMException e) {
}
}
}
return IBinding.EMPTY_BINDING_ARRAY;
}
/**
* Helper function for resolveUnknownType().
* Returns the type of a binding.
*/
private static IType typeForBinding(IBinding binding) {
if (binding instanceof IType) {
return (IType) binding;
} else if (binding instanceof IVariable) {
return ((IVariable) binding).getType();
} else if (binding instanceof IEnumerator) {
return ((IEnumerator) binding).getType();
} else if (binding instanceof IFunction) {
return ((IFunction) binding).getType();
}
return null;
}
/**
* Given an unknown type, heuristically tries to find a concrete type
* (i.e. not an unknown type) corresponding to it.
*
* Returns null if no heuristic resolution could be performed.
*
* Multiple rounds of resolution are performed, as the result of a single
* round may yield a type which is still dependent. Resolution stops when
* a concrete type is found, or the resolution of the last resolution
* round is the same as the result of the previous resolution round.
* In between each round, typedefs are unwrapped.
*/
public static IType resolveUnknownType(ICPPUnknownType type) {
return resolveUnknownType(type, SemanticUtil.TDEF | SemanticUtil.CVTYPE);
}
/**
* Similar to resolveUnknownType(type, point), but allows specifying
* things other than typedefs to unwrap between rounds of resolution
* (e.g. references).
*/
private static IType resolveUnknownType(ICPPUnknownType type, int unwrapOptions) {
while (true) {
Set<HeuristicLookup> lookupSet = new HashSet<>();
IType resolvedType = resolveUnknownTypeOnce(type, lookupSet);
resolvedType = SemanticUtil.getNestedType(resolvedType, unwrapOptions);
if (resolvedType != type && resolvedType instanceof ICPPUnknownType) {
type = (ICPPUnknownType) resolvedType;
continue;
}
return resolvedType;
}
}
/**
* Heuristically choose between the primary template and any partial specializations
* for a deferred template instance.
*/
private static ICPPClassTemplate chooseTemplateForDeferredInstance(ICPPDeferredClassInstance instance) {
ICPPClassTemplate template = instance.getClassTemplate();
if (!instance.isExplicitSpecialization()) {
try {
IBinding partial = CPPTemplates.selectSpecialization(template,
instance.getTemplateArguments(), false);
if (partial != null && partial instanceof ICPPTemplateInstance)
return (ICPPClassTemplate) ((ICPPTemplateInstance) partial).getTemplateDefinition();
} catch (DOMException e) {
}
}
return template;
}
private static IType resolveEvalType(ICPPEvaluation evaluation, Set<HeuristicLookup> lookupSet) {
if (evaluation instanceof EvalUnary) {
EvalUnary unary = (EvalUnary) evaluation;
// Handle the common case of a dependent type representing the
// result of dereferencing another dependent type.
if (unary.getOperator() == IASTUnaryExpression.op_star) {
IType argument = unary.getArgument().getType();
if (argument instanceof ICPPUnknownType) {
IType resolved = resolveUnknownType((ICPPUnknownType) argument);
if (resolved instanceof IPointerType) {
return ((IPointerType) resolved).getType();
}
}
}
} else if (evaluation instanceof EvalID) {
EvalID id = (EvalID) evaluation;
ICPPEvaluation fieldOwner = id.getFieldOwner();
if (fieldOwner != null) {
IType fieldOwnerType = fieldOwner.getType();
IBinding[] candidates = lookInside(fieldOwnerType, id.isPointerDeref(), id.getName(),
id.getTemplateArgs(), lookupSet);
if (candidates.length > 0) {
// If there is more than one candidate, for now just
// choose the first one. A better thing to do would
// be to perform heuristic overload resolution (TODO).
return typeForBinding(candidates[0]);
}
}
} else if (evaluation instanceof EvalFunctionCall) {
EvalFunctionCall evalFunctionCall = (EvalFunctionCall) evaluation;
ICPPEvaluation function = evalFunctionCall.getArguments()[0];
IType functionType = function.getType();
if (functionType instanceof ICPPUnknownType) {
functionType = resolveUnknownType((ICPPUnknownType) functionType);
}
return ExpressionTypes.typeFromFunctionCall(functionType);
} else if (evaluation instanceof EvalMemberAccess) {
IBinding member = ((EvalMemberAccess) evaluation).getMember();
// Presumably the type will be unknown. That's fine, it will be
// resolved during subsequent resolution rounds.
return typeForBinding(member);
} else if (evaluation instanceof EvalTypeId) {
EvalTypeId evalTypeId = (EvalTypeId) evaluation;
IType result = evalTypeId.getInputType();
if (evalTypeId.representsNewExpression()) {
result = new CPPPointerType(result);
}
return result;
} else if (evaluation instanceof EvalBinding) {
return evaluation.getType();
}
// TODO(nathanridge): Handle more cases.
return null;
}
/**
* Helper function for {@link #resolveUnknownType} which does one round of resolution.
*/
private static IType resolveUnknownTypeOnce(ICPPUnknownType type, Set<HeuristicLookup> lookupSet) {
if (type instanceof ICPPDeferredClassInstance) {
ICPPDeferredClassInstance deferredInstance = (ICPPDeferredClassInstance) type;
return chooseTemplateForDeferredInstance(deferredInstance);
} else if (type instanceof TypeOfDependentExpression) {
ICPPEvaluation evaluation = ((TypeOfDependentExpression) type).getEvaluation();
return resolveEvalType(evaluation, lookupSet);
} else if (type instanceof ICPPUnknownMemberClass) {
ICPPUnknownMemberClass member = (ICPPUnknownMemberClass) type;
IType ownerType = member.getOwnerType();
IBinding[] candidates = lookInside(ownerType, false, member.getNameCharArray(), null, lookupSet);
if (candidates.length == 1) {
if (candidates[0] instanceof IType) {
IType result = (IType) candidates[0];
if (type instanceof ICPPUnknownMemberClassInstance) {
ICPPTemplateArgument[] args = ((ICPPUnknownMemberClassInstance) type).getArguments();
if (result instanceof ICPPClassTemplate) {
result = (IType) CPPTemplates.instantiate((ICPPClassTemplate) result, args);
} else if (result instanceof ICPPAliasTemplate) {
result = (IType) CPPTemplates.instantiateAliasTemplate((ICPPAliasTemplate) result,
args);
}
}
return result;
}
}
}
return null;
}
/**
* Given an unknown binding, heuristically tries to find concrete bindings (i.e. not unknown bindings)
* corresponding to it.
*
* Returns an empty array if no heuristic resolution could be performed.
*/
public static IBinding[] resolveUnknownBinding(ICPPUnknownBinding binding) {
if (binding instanceof ICPPDeferredClassInstance) {
return new IBinding[] { chooseTemplateForDeferredInstance((ICPPDeferredClassInstance) binding) };
} else if (binding instanceof ICPPUnknownMember) {
Set<HeuristicLookup> lookupSet = new HashSet<>();
return lookInside(((ICPPUnknownMember) binding).getOwnerType(), false,
binding.getNameCharArray(), null, lookupSet);
} else if (binding instanceof ICPPUnknownType) {
IType resolved = resolveUnknownType((ICPPUnknownType) binding);
if (resolved != binding && resolved instanceof IBinding) {
return new IBinding[] { (IBinding) resolved };
}
}
return IBinding.EMPTY_BINDING_ARRAY;
}
}