summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Eidsness2013-11-27 14:59:13 (EST)
committer Doug Schaefer2013-11-28 23:37:18 (EST)
commit08c7d10763cae343176d74699cc732da13dfe6bf (patch)
treeff7b91ccc05a9cd6d1b6574e90fe0adc75c43b01
parent026b9325f08f899d0b770b2e0968446599144c74 (diff)
downloadorg.eclipse.cdt-08c7d10763cae343176d74699cc732da13dfe6bf.zip
org.eclipse.cdt-08c7d10763cae343176d74699cc732da13dfe6bf.tar.gz
org.eclipse.cdt-08c7d10763cae343176d74699cc732da13dfe6bf.tar.bz2
Bug 422765: New method to find IBindings from qualifiedNamerefs/changes/67/19067/2
This creates a new method in CPPSemantics that will lookup a list of IBindings from a qualifiedName. E.g., given: namespace A { namespace B { int b; } } CPPSemantics.findBindingsByQualifiedName(scope, "A::B::b"); will return an array with the CPPVariable binding for b. This commit contains a new test case for various cases that I've thought about. I had expected that by using the existing lookup functions (in CPPSemantics) I wouldn't have to think too hard about various matches. However, the existing functions didn't work quite the way that I expected. Change-Id: I8a5aacba4a02d87f71ed4698aa432c3161395a31 Signed-off-by: Andrew Eidsness <eclipse@jfront.com> Reviewed-on: https://git.eclipse.org/r/19067 Tested-by: Hudson CI Reviewed-by: Doug Schaefer <dschaefer@qnx.com> IP-Clean: Doug Schaefer <dschaefer@qnx.com>
-rw-r--r--core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPTests.java64
-rw-r--r--core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPSemantics.java127
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 029f08a..e9e55cf 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 8f9806e..70af834 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();