blob: 9e97a2a1ff4204c44c9e6831c4b94c72f22494e5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2015 Wind River Systems, Inc. and others.
* 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:
* Markus Schorn - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp.semantics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.eclipse.cdt.core.dom.IName;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNameOwner;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IProblemBinding;
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.core.parser.util.CharArrayObjectMap;
import org.eclipse.cdt.internal.core.dom.parser.ProblemBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalUnknownScope;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding;
/**
* Helper class for performing the base class lookup. First a directed graph without loops is
* computed to represent the base class hierarchy up to those bases for which the lookup finds
* matches. Next, from these leaves we search for virtual bases that are hidden. With this
* information the matches are extracted from the graph.
*/
class BaseClassLookup {
public static void lookupInBaseClasses(LookupData data, ICPPClassScope classScope) {
if (classScope == null)
return;
final ICPPClassType classType= classScope.getClassType();
if (classType == null)
return;
final HashMap<IScope, BaseClassLookup> infoMap = new HashMap<>();
BaseClassLookup rootInfo= lookupInBaseClass(data, null, false, classType, infoMap, 0);
if (data.contentAssist) {
rootInfo.collectResultForContentAssist(data);
} else {
hideVirtualBases(rootInfo, infoMap);
IBinding[] result= rootInfo.collectResult(data, true, IBinding.EMPTY_BINDING_ARRAY);
if (data.problem == null) {
data.foundItems = ArrayUtil.addAll((Object[]) data.foundItems, result);
} else if (result.length > 0) {
data.problem.setCandidateBindings(result);
}
// verifyResult(data, result);
}
}
private final ICPPClassType fClassType;
private IBinding[] fBindings;
private List<BaseClassLookup> fChildren= Collections.emptyList();
private BitSet fVirtual;
private boolean fHiddenAsVirtualBase;
private boolean fPropagationDone;
private boolean fCollected;
private boolean fCollectedAsRegularBase;
private final IASTNode fLookupPoint;
private BaseClassLookup(ICPPClassType type, IASTNode lookupPoint) {
fClassType= type;
fLookupPoint= lookupPoint;
}
ICPPClassType getClassType() {
return fClassType;
}
IBinding[] getResult() {
return fBindings;
}
boolean containsVirtualBase() {
return (fVirtual != null && fVirtual.nextSetBit(0) >= 0);
}
boolean hasMatches() {
return fBindings != null && fBindings.length > 0 && fBindings[0] != null;
}
public void addBase(boolean virtual, BaseClassLookup baseInfo) {
if (virtual && fHiddenAsVirtualBase)
return;
if (fChildren.isEmpty()) {
fChildren= new ArrayList<>();
fVirtual= new BitSet();
}
fVirtual.set(fChildren.size(), virtual);
fChildren.add(baseInfo);
}
public void setResult(IBinding[] bindings) {
fBindings= bindings;
}
public void setHiddenAsVirtualBase() {
fHiddenAsVirtualBase= true;
}
public void propagateHiddenAsVirtual() {
if (fPropagationDone)
return;
fPropagationDone= true;
for (int i= 0; i < fChildren.size(); i++) {
BaseClassLookup child = fChildren.get(i);
if (fVirtual.get(i)) {
child.setHiddenAsVirtualBase();
}
child.propagateHiddenAsVirtual();
}
}
public boolean containsNonStaticMember() {
for (IBinding binding : fBindings) {
if (binding == null)
return false;
if (binding instanceof ICPPMember) {
if (!((ICPPMember) binding).isStatic())
return true;
}
}
return false;
}
static BaseClassLookup lookupInBaseClass(LookupData data, ICPPClassScope baseClassScope,
boolean isVirtual, ICPPClassType root, HashMap<IScope, BaseClassLookup> infoMap, int depth) {
if (depth++ > CPPSemantics.MAX_INHERITANCE_DEPTH)
return null;
if (baseClassScope != null) {
BaseClassLookup info= infoMap.get(baseClassScope);
if (info != null) {
// Avoid loops.
if (info.getResult() == null) {
data.problem = new ProblemBinding(null, IProblemBinding.SEMANTIC_CIRCULAR_INHERITANCE,
root.getNameCharArray());
return null;
}
return info;
}
}
// This is the first time to handle the class.
BaseClassLookup result;
IBinding[] matches= IBinding.EMPTY_BINDING_ARRAY;
if (baseClassScope == null) {
result= new BaseClassLookup(root, data.getLookupPoint());
infoMap.put(root.getCompositeScope(), result);
} else {
result= new BaseClassLookup(baseClassScope.getClassType(), data.getLookupPoint());
infoMap.put(baseClassScope, result);
try {
// Determine the name qualifier if the lookup name is a definition.
ICPPASTNameSpecifier nameQualifier = null;
if (data.qualified) {
// Check if the name qualifier is in agreement with the base class name.
IASTName lookupName = data.getLookupName();
if (lookupName != null && lookupName.getPropertyInParent() == ICPPASTQualifiedName.SEGMENT_NAME &&
lookupName.getRoleOfName(false) == IASTNameOwner.r_definition) {
ICPPASTQualifiedName qName = (ICPPASTQualifiedName) lookupName.getParent();
ICPPASTNameSpecifier[] qualifiers = qName.getQualifier();
for (int i = 0; i < qualifiers.length && lookupName != qualifiers[i]; i++) {
nameQualifier = qualifiers[i];
}
}
}
IName baseClassScopeName = baseClassScope.getScopeName();
if (nameQualifier == null || (baseClassScopeName != null && Arrays.equals(baseClassScopeName.getSimpleID(), nameQualifier.toCharArray()))) {
IBinding[] members= CPPSemantics.getBindingsFromScope(baseClassScope, data);
if (members != null && members.length > 0 && members[0] != null) {
if (data.isPrefixLookup()) {
matches= members;
} else {
result.setResult(members);
return result;
}
}
}
} catch (DOMException e) {
// Continue the lookup.
}
}
// There is no result in the baseClass itself or we do content assist, we have to examine
// its base classes.
ICPPClassType baseClass= result.getClassType();
if (baseClass != null) {
ICPPBase[] grandBases= baseClass.getBases();
if (grandBases != null && grandBases.length > 0) {
HashSet<IBinding> grandBaseBindings= null;
BitSet selectedBases= null;
if (grandBases.length > 1) {
grandBaseBindings= new HashSet<>();
// If we have reachable bases, then ignore the others.
selectedBases = selectPreferredBases(data, grandBases);
}
for (int i = 0; i < grandBases.length; i++) {
ICPPBase grandBase = grandBases[i];
if (selectedBases != null && !selectedBases.get(i))
continue;
IBinding grandBaseBinding = grandBase.getBaseClass();
if (!(grandBaseBinding instanceof ICPPClassType)) {
// 14.6.2.3 scope is not examined.
if (grandBaseBinding instanceof ICPPUnknownBinding) {
if (data.skippedScope == null)
data.skippedScope= root;
}
continue;
}
ICPPClassType grandBaseClass = (ICPPClassType) grandBaseBinding;
if (data.fHeuristicBaseLookup && grandBaseClass instanceof ICPPDeferredClassInstance) {
// Support content assist for members of deferred instances.
grandBaseClass= ((ICPPDeferredClassInstance) grandBaseClass).getClassTemplate();
}
if (grandBaseBindings != null && !grandBaseBindings.add(grandBaseClass))
continue;
final IScope grandBaseScope= grandBaseClass.getCompositeScope();
if (grandBaseScope == null || grandBaseScope instanceof ICPPInternalUnknownScope) {
// 14.6.2.3 scope is not examined.
if (data.skippedScope == null)
data.skippedScope= root;
continue;
}
if (!(grandBaseScope instanceof ICPPClassScope))
continue;
BaseClassLookup baseInfo= lookupInBaseClass(data, (ICPPClassScope) grandBaseScope,
grandBase.isVirtual(), root, infoMap, depth);
if (baseInfo != null)
result.addBase(grandBase.isVirtual(), baseInfo);
}
}
}
result.setResult(matches);
return result;
}
private static BitSet selectPreferredBases(LookupData data, ICPPBase[] grandBases) {
if (data.contentAssist)
return null;
BitSet selectedBases;
selectedBases= new BitSet(grandBases.length);
IName baseName= null;
for (int i = 0; i < grandBases.length; i++) {
ICPPBase nbase = grandBases[i];
if (nbase instanceof IProblemBinding)
continue;
final IName nbaseName = nbase.getClassDefinitionName();
int cmp= baseName == null ? 0 : CPPSemantics.compareByRelevance(data, baseName, nbaseName);
if (cmp <= 0) {
if (cmp < 0) {
selectedBases.clear();
baseName= nbaseName;
}
selectedBases.set(i);
}
}
return selectedBases;
}
static void hideVirtualBases(BaseClassLookup rootInfo, HashMap<IScope, BaseClassLookup> infoMap) {
boolean containsVirtualBase= false;
final BaseClassLookup[] allInfos = infoMap.values().toArray(new BaseClassLookup[infoMap.size()]);
for (BaseClassLookup info : allInfos) {
if (info.containsVirtualBase()) {
containsVirtualBase= true;
break;
}
}
if (containsVirtualBase) {
for (BaseClassLookup info : allInfos) {
if (info.hasMatches()) {
info.hideVirtualBases(infoMap, 0);
}
}
}
}
void hideVirtualBases(HashMap<IScope, BaseClassLookup> infoMap, int depth) {
if (depth++ > CPPSemantics.MAX_INHERITANCE_DEPTH)
return;
if (fClassType != null) {
ICPPBase[] bases= null;
bases= fClassType.getBases();
if (bases != null && bases.length > 0) {
for (ICPPBase base : bases) {
IBinding baseBinding = base.getBaseClass();
if (!(baseBinding instanceof ICPPClassType)) {
continue;
}
final ICPPClassType baseClass = (ICPPClassType) baseBinding;
final IScope baseScope= baseClass.getCompositeScope();
if (!(baseScope instanceof ICPPClassScope))
continue;
BaseClassLookup baseInfo= infoMap.get(baseScope);
if (baseInfo != null) {
if (base.isVirtual()) {
baseInfo.setHiddenAsVirtualBase();
}
baseInfo.propagateHiddenAsVirtual();
} else {
// Mark to catch recursions.
baseInfo= new BaseClassLookup(baseClass, fLookupPoint);
infoMap.put(baseScope, baseInfo);
baseInfo.hideVirtualBases(infoMap, depth);
}
}
}
}
}
public void collectResultForContentAssist(LookupData data) {
if (fCollected)
return;
fCollected= true;
@SuppressWarnings("unchecked")
final CharArrayObjectMap<Object> resultMap = (CharArrayObjectMap<Object>) data.foundItems;
data.foundItems = CPPSemantics.mergePrefixResults(resultMap, fBindings, true);
for (int i= 0; i < fChildren.size(); i++) {
BaseClassLookup child = fChildren.get(i);
child.collectResultForContentAssist(data);
}
}
private IBinding[] collectResult(LookupData data, boolean asVirtualBase, IBinding[] result) {
if (asVirtualBase) {
if (fHiddenAsVirtualBase)
return result;
} else {
if (fCollectedAsRegularBase && data.problem == null && containsNonStaticMember()) {
data.problem= new ProblemBinding(data.getLookupName(), IProblemBinding.SEMANTIC_AMBIGUOUS_LOOKUP);
}
fCollectedAsRegularBase= true;
}
if (fCollected)
return result;
fCollected= true;
int numBindingsToAdd = 0;
for (int i = 0; i < fBindings.length; i++) {
IBinding binding = fBindings[i];
if (binding == null)
break;
if (!ArrayUtil.contains(result, binding))
fBindings[numBindingsToAdd++] = binding;
}
if (numBindingsToAdd < fBindings.length)
fBindings[numBindingsToAdd] = null;
boolean possibleAmbiguity = false;
if (result.length > 0 && numBindingsToAdd > 0 && data.problem == null) {
// Matches are found in more than one base class - this is usually
// an indication of ambiguity (but see below).
possibleAmbiguity = true;
}
result= ArrayUtil.addAll(result, fBindings);
if (possibleAmbiguity) {
// [temp.local] p4:
// A lookup that finds an injected-class-name can result in an
// ambiguity in certain cases (for example, if it is found in
// more than one base class). If all of the injected-class-names
// that are found refer to specializations of the same class
// template, and if the name is used as a template-name, the
// reference refers to the class template itself and not a
// specialization thereof, and is not ambiguous.
result = collapseInjectedClassNames(data, result);
if (result.length > 1) {
data.problem= new ProblemBinding(data.getLookupName(),
IProblemBinding.SEMANTIC_AMBIGUOUS_LOOKUP, result);
}
}
for (int i= 0; i < fChildren.size(); i++) {
BaseClassLookup child = fChildren.get(i);
result= child.collectResult(data, fVirtual.get(i), result);
}
return result;
}
// If all bindings in 'result' are instances of the same class template,
// collapse them to the class template itself. Only applies if the lookup
// is for a name used as a template-name.
private IBinding[] collapseInjectedClassNames(LookupData data, IBinding[] result) {
IASTName lookupName = data.getLookupName();
if (lookupName == null || lookupName.getPropertyInParent() != ICPPASTTemplateId.TEMPLATE_NAME) {
// Name not used as a template-name.
return result;
}
ICPPTemplateDefinition template = null;
for (IBinding binding : result) {
if (binding instanceof ICPPClassType && binding instanceof ICPPTemplateInstance) {
ICPPTemplateDefinition specialized =
(ICPPTemplateDefinition) ((ICPPTemplateInstance) binding).getSpecializedBinding();
if (template == null) {
template = specialized;
continue;
}
if (template == specialized) {
continue;
}
}
return result;
}
if (template != null) {
return new IBinding[] { template };
}
return result;
}
}