diff options
2 files changed, 184 insertions, 7 deletions
diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPTests.java index 029f08a5b3d..e9e55cf8d9f 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPTests.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPTests.java @@ -9809,7 +9809,7 @@ public class AST2CPPTests extends AST2TestBase { public void testFriendTemplateParameter() throws Exception { parseAndCheckBindings(); } - + // struct foo { // foo(); // ~foo(); @@ -10176,7 +10176,7 @@ public class AST2CPPTests extends AST2TestBase { public void testIsBaseOf_399353() throws Exception { parseAndCheckBindings(getAboveComment(), CPP, true); } - + // struct base {}; // struct derived : base {}; // typedef derived derived2; @@ -10334,7 +10334,7 @@ public class AST2CPPTests extends AST2TestBase { ICPPClassType privateNestedClass = bh.assertNonProblem("privateNestedClass"); assertVisibility(ICPPClassType.v_private, aClass.getVisibility(privateNestedClass)); } - + // int main() { // int i = 0; // __sync_bool_compare_and_swap(& i, 0, 1); @@ -10344,12 +10344,12 @@ public class AST2CPPTests extends AST2TestBase { public void testGNUSyncBuiltins_bug389578() throws Exception { parseAndCheckBindings(getAboveComment(), CPP, true); } - + // class Waldo { // typedef int type; // static int value; // }; - // + // // int main() { // Waldo w; // decltype(w)::type i; @@ -10385,15 +10385,65 @@ public class AST2CPPTests extends AST2TestBase { // typedef underlying_type<e_long>::type loong_type; public void testUnderlyingTypeBuiltin_bug411196() throws Exception { BindingAssertionHelper helper = getAssertionHelper(); - + assertSameType((ITypedef) helper.assertNonProblem("short1_type"), CPPVisitor.SHORT_TYPE); assertSameType((ITypedef) helper.assertNonProblem("short2_type"), CPPVisitor.SHORT_TYPE); assertSameType((ITypedef) helper.assertNonProblem("scoped_type"), CPPVisitor.INT_TYPE); - + assertSameType((ITypedef) helper.assertNonProblem("unsigned_type"), CPPVisitor.UNSIGNED_INT); assertSameType((ITypedef) helper.assertNonProblem("int_type"), CPPVisitor.INT_TYPE); assertSameType((ITypedef) helper.assertNonProblem("ulong_type"), CPPVisitor.UNSIGNED_LONG); assertSameType((ITypedef) helper.assertNonProblem("loong_type"), CPPVisitor.LONG_TYPE); } + + // namespace A { + // int a; + // namespace B { + // int b; + // namespace C { + // int c; + // } + // namespace A { + // int a; + // } + // } + // } + public void testQualifiedNameLookup() throws Exception { + IASTTranslationUnit tu = parse(getAboveComment(), CPP); + + IScope scope = tu.getScope(); + assertNotNull(scope); + + IBinding[] bindings = CPPSemantics.findBindingsForQualifiedName(scope, " A::a"); + assertNotNull(bindings); + assertEquals(1, bindings.length); + IBinding a = bindings[0]; + assertEquals("a", a.getName()); + + bindings = CPPSemantics.findBindingsForQualifiedName(scope, "A::B::b "); + assertNotNull(bindings); + assertEquals(1, bindings.length); + IBinding b = bindings[0]; + assertEquals("b", b.getName()); + + bindings = CPPSemantics.findBindingsForQualifiedName(scope, "A:: B ::C::c"); + assertNotNull(bindings); + assertEquals(1, bindings.length); + IBinding c = bindings[0]; + assertEquals("c", c.getName()); + + // From the level of c, there should be two A::a (::A::a and ::A::B::A::a). + IScope scopeC = c.getScope(); + assertNotNull(scopeC); + bindings = CPPSemantics.findBindingsForQualifiedName(scopeC, "A::a"); + assertNotNull(bindings); + assertEquals(2, bindings.length); + + // From the level of c, there should be only one ::A::a. + assertNotNull(scopeC); + bindings = CPPSemantics.findBindingsForQualifiedName(scopeC, "::A::a"); + assertNotNull(bindings); + assertEquals(1, bindings.length); + } } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPSemantics.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPSemantics.java index 8f9806e5c73..70af83405a3 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPSemantics.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPSemantics.java @@ -32,12 +32,16 @@ import static org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUti import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.IName; import org.eclipse.cdt.core.dom.ast.ASTNodeProperty; import org.eclipse.cdt.core.dom.ast.ASTVisitor; @@ -249,6 +253,22 @@ public class CPPSemantics { // special return value for costForFunctionCall private static final FunctionCost CONTAINS_DEPENDENT_TYPES = new FunctionCost(null, 0, null); + // A regular expression for matching qualified names. This allows for optional global qualification + // (leading ::) and then separates the first part of the name from the rest (if present). There are + // three capture groups: + // (1) If the input name specifies the global namespace (leading ::) then capture group 1 will + // be ::. Group 1 will be null otherwise. + // (2) The text of the first component of the qualified name, including leading :: if present in + // the input string. Leading and trailing whitespace is trimmed. There is no effort to check + // that the name contains valid C++ identifier characters. + // (3) The text of everything after the first component of the qualified name. + // + // E.g., -- Input Name -- ---- Capture Groups ---- + // "::nsA::nsB::b" => { "::", "nsA", "nsB::b" } + // "a" => { null, "a", null } + // ":: i" => { "::", "i", null } + private static final Pattern QUALNAME_REGEX = Pattern.compile("^\\s*(::)?\\s*([^\\s:]+)\\s*(?:::(.*))?$"); //$NON-NLS-1$ + static protected IBinding resolveBinding(IASTName name) { if (traceBindingResolution) { for (int i = 0; i < traceIndent; i++) @@ -3574,6 +3594,113 @@ public class CPPSemantics { return contentAssistLookup(data, nsScopes); } + private static IScope getLookupScope(IASTNode node) { + if (node == null) + return null; + + if (node instanceof IASTCompositeTypeSpecifier) + return ((IASTCompositeTypeSpecifier) node).getScope(); + + if (node instanceof ICPPASTNamespaceDefinition) + return ((ICPPASTNamespaceDefinition) node).getScope(); + + if (!(node instanceof ICPPInternalBinding)) + return null; + + IASTNode defn = ((ICPPInternalBinding) node).getDefinition(); + if (defn == null) + return null; + + return getLookupScope(defn.getParent()); + } + + private static IScope getLookupScope(IBinding binding) { + if (binding == null) + return null; + + if (binding instanceof IASTCompositeTypeSpecifier) + return ((IASTCompositeTypeSpecifier) binding).getScope(); + + if (!(binding instanceof ICPPInternalBinding)) + return null; + + IASTNode defn = ((ICPPInternalBinding) binding).getDefinition(); + if (defn == null) + return null; + + return getLookupScope(defn.getParent()); + } + + /** + * Use C++ lookup semantics to find the possible bindings for the given qualified name starting + * in the given scope. + */ + public static IBinding[] findBindingsForQualifiedName(IScope scope, String qualifiedName) { + + // Return immediately if the qualifiedName does not match a known format. + Matcher m = QUALNAME_REGEX.matcher(qualifiedName); + if (!m.matches()) + return IBinding.EMPTY_BINDING_ARRAY; + + // If the qualified name is rooted in the global namespace, then navigate to that scope. + boolean isGlobal = m.group(1) != null; + if (isGlobal) { + IScope global = scope; + try { + while(global.getParent() != null) + global = global.getParent(); + } catch(DOMException e) { + CCorePlugin.log(e); + } + scope = global; + } + + Set<IBinding> bindings = new HashSet<IBinding>(); + + // Look for the name in the given scope. + findBindingsForQualifiedName(scope, qualifiedName, bindings); + + // If the qualified name is not rooted in the global namespace (with a leading ::), then + // look at all parent scopes. + if (!isGlobal) + try { + while(scope != null) { + scope = scope.getParent(); + if (scope != null) + findBindingsForQualifiedName(scope, qualifiedName, bindings); + } + } catch (DOMException e) { + CCorePlugin.log(e); + } + + return bindings.size() <= 0 ? IBinding.EMPTY_BINDING_ARRAY : bindings.toArray(new IBinding[bindings.size()]); + } + + private static void findBindingsForQualifiedName(IScope scope, String qualifiedName, Collection<IBinding> bindings) { + // Split the qualified name into the first part (before the first :: qualifier) and the rest. All + // bindings for the first part are found and their scope is used to find the rest of the name. When + // the call tree gets to a leaf (non-qualified name) then a simple lookup happens and all matching + // bindings are added to the result. + + Matcher m = QUALNAME_REGEX.matcher(qualifiedName); + if (!m.matches()) + return; + + String part1 = m.group(2); + String part2 = m.group(3); + + // When we're down to a single component name, then use the normal lookup method. + if (part2 == null || part2.isEmpty()) { + bindings.addAll(Arrays.asList(findBindings(scope, part1, false))); + return; + } + + // Find all bindings that match the first part of the name. For each such binding, + // lookup the second part of the name. + for(IBinding binding : CPPSemantics.findBindings(scope, part1, false)) + findBindingsForQualifiedName(getLookupScope(binding), part2, bindings); + } + private static ICPPScope getNamespaceScope(CPPASTTranslationUnit tu, String[] namespaceParts, IASTNode point) throws DOMException { ICPPScope nsScope= tu.getScope(); |