From a512bbb01128ee2707cf02e3a6e5d871c4466197 Mon Sep 17 00:00:00 2001 From: Andrew Eidsness Date: Thu, 2 Jan 2014 14:18:10 -0500 Subject: [PATCH] Bug 424499: Find References does not work for Qt signals and slots The QtASTVisitor is trying to use the QtIndex during indexing. Any results available at this time are based on the state of the AST the last time the code was indexed. This adds a test case to reproduce the problem. The test cases indexes the project one time. It should find two references to the signal. If the QtIndex data is stale, then it will find 0 references. This also replaces the code that looks for QObject::connect function calls. The proper behaviour is to find all overloads of #connect as well as references with QObject::disconnect (all overloads) function calls. A new test case checks for references in all overloads of #connect and #disconnect function calls. Change-Id: I28fc4213d6dddff10f81a6bd3ef01e24c74f31db Signed-off-by: Andrew Eidsness Reviewed-on: https://git.eclipse.org/r/20223 Tested-by: Hudson CI Reviewed-by: Doug Schaefer IP-Clean: Doug Schaefer --- .../eclipse/cdt/internal/qt/core/ASTUtil.java | 21 +- .../cdt/internal/qt/core/QtFunctionCall.java | 182 +++++++++++ .../internal/qt/core/QtMethodReference.java | 179 +++++++++++ .../qt/core/QtSignalSlotReference.java | 108 ------- .../core/QtSignalSlotReferenceLocation.java | 82 ----- .../qt/core/QtSignalSlotReferenceName.java | 287 ------------------ .../qt/core/pdom/ASTDelegatedName.java | 22 +- .../qt/core/pdom/ASTNameReference.java | 9 +- .../internal/qt/core/pdom/QtASTVisitor.java | 40 +-- .../org/eclipse/cdt/qt/core/QtKeywords.java | 72 +++-- .../META-INF/MANIFEST.MF | 5 +- .../eclipse/cdt/qt/tests/BaseQtTestCase.java | 11 +- .../eclipse/cdt/qt/tests/QObjectTests.java | 188 +++++++++++- .../cdt/qt/tests/QtRegressionTests.java | 83 +++++ 14 files changed, 727 insertions(+), 562 deletions(-) create mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCall.java create mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtMethodReference.java delete mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java delete mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java delete mode 100644 qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java index d75ebe75aac..7fc4bbaa414 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java @@ -66,10 +66,9 @@ public class ASTUtil { // sig1( // int // ), ... - // The two patterns are nearly identical. The difference is because the first is for matching SIGNAL/ - // SLOT expansions. The second is for matching the argument to that expansion. - public static final Pattern Regex_SignalSlotExpansion = Pattern.compile("(?s)(SIGNAL|SLOT)\\s*\\(\\s*(.*?)\\s*\\)\\s*"); - public static final Pattern Regex_FunctionCall = Pattern.compile("(?s)\\s*(.*)\\s*\\(\\s*(.*?)\\s*\\)\\s*"); + // The regex trims leading and trailing whitespace within the expansion parameter. This is needed + // so that the start of the capture group provides the proper offset into the expansion. + public static final Pattern Regex_MacroExpansion = Pattern.compile("(?s)([_a-zA-Z]\\w*)\\s*\\(\\s*(.*?)\\s*\\)\\s*"); public static IType getBaseType(IType type) { while (type instanceof ITypeContainer) @@ -118,11 +117,11 @@ public class ASTUtil { /** * Does not return null. */ - public static Collection findMethods(IQObject qobj, QtSignalSlotReference ref) { + public static Collection findMethods(IQObject qobj, QtMethodReference ref) { Set bindings = new LinkedHashSet(); Iterable methods = null; - switch(ref.type) + switch(ref.getType()) { case Signal: methods = qobj.getSignals().withoutOverrides(); @@ -133,7 +132,7 @@ public class ASTUtil { } if (methods != null) { - String qtNormalizedSig = QtMethodUtil.getQtNormalizedMethodSignature(ref.signature); + String qtNormalizedSig = QtMethodUtil.getQtNormalizedMethodSignature(ref.getRawSignature()); if (qtNormalizedSig == null) return bindings; @@ -145,7 +144,7 @@ public class ASTUtil { return bindings; } - public static IBinding resolveFunctionBinding(IASTFunctionCallExpression fnCall) { + public static T resolveFunctionBinding(Class cls, IASTFunctionCallExpression fnCall) { IASTName fnName = null; IASTExpression fnNameExpr = fnCall.getFunctionNameExpression(); if (fnNameExpr instanceof IASTIdExpression) @@ -153,7 +152,11 @@ public class ASTUtil { else if (fnNameExpr instanceof ICPPASTFieldReference) fnName = ((ICPPASTFieldReference) fnNameExpr).getFieldName(); - return fnName == null ? null : fnName.resolveBinding(); + IBinding binding = fnName == null ? null : fnName.resolveBinding(); + if (binding == null) + return null; + + return cls.isAssignableFrom(binding.getClass()) ? cls.cast(binding) : null; } public static ICPPASTVisibilityLabel findVisibilityLabel(ICPPMethod method, IASTNode ast) { diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCall.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCall.java new file mode 100644 index 00000000000..62b8954d6d1 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCall.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2014 QNX Software Systems 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 + */ +package org.eclipse.cdt.internal.qt.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; +import org.eclipse.cdt.core.dom.ast.IASTInitializerClause; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IBasicType; +import org.eclipse.cdt.core.dom.ast.IEnumeration; +import org.eclipse.cdt.core.dom.ast.IType; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter; +import org.eclipse.cdt.qt.core.QtKeywords; + +/** + * Extracts required information from FunctionCallExpressions that call + * QObject::connect. This implementation handles all overloads of QObject::connect + * except the QMetaMethod related ones. QMetaMethods cannot be statically analyzed + * so they are ignored. + *

+ * The binding is found by identifying the overload and then looking at the appropriate + * parameters. + */ +public class QtFunctionCall { + + private QtFunctionCall() { + } + + protected static IASTNode safeArgsAt(IASTNode[] args, int index) { + return args.length > index ? args[index] : null; + } + + /** + * Returns a collection of all Qt method references within the given function call. Returns + * null if there are no Qt method references. + */ + public static Collection getReferences(IASTFunctionCallExpression call) { + ICPPFunction function = ASTUtil.resolveFunctionBinding(ICPPFunction.class, call); + if (function == null) + return null; + + if (QtKeywords.is_QObject_connect(function)) + return getReferencesInConnect(function, call); + if (QtKeywords.is_QObject_disconnect(function)) + return getReferencesInDisconnect(function, call); + return null; + } + + private static Collection getReferencesInConnect(ICPPFunction function, IASTFunctionCallExpression call) { + if (function == null) + return null; + + // There are 3 overloads of QObject::connect (Qt 4.8.4). They can be + // distinguished by examining + // the type of the forth parameter. + // connect( , , , const char *, ) + // connect( , , , QMetaMethod&, ) + // connect( , , , Qt::ConnectionType = ) + ICPPParameter[] params = function.getParameters(); + if (params.length < 4) + return null; + + IASTInitializerClause[] args = call.getArguments(); + IType type3 = ASTUtil.getBaseType(params[3].getType()); + + // static bool connect( const QObject *sender, const QMetaMethod &signal, + // const QObject *receiver, const QMetaMethod &method, + // Qt::ConnectionType type = Qt::AutoConnection ) + if (QtKeywords.isQMetaMethod(type3)) { + // QMetaMethod cannot be statically analyzed. + return null; + } + + // Otherwise find the signal and member parameters based on the overload. + IASTName signal = null; + IASTName member = null; + + // static bool connect( const QObject *sender, const char *signal, + // const QObject *receiver, const char *member, + // Qt::ConnectionType = Qt::AutoConnection ); + if (type3 instanceof IBasicType && ((IBasicType) type3).getKind() == IBasicType.Kind.eChar) { + signal = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 0)), safeArgsAt(args, 1)); + member = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 2)), safeArgsAt(args, 3)); + } + + // inline bool connect( const QObject *sender, const char *signal, + // const char *member, + // Qt::ConnectionType type = Qt::AutoConnection ) const; + else if (type3 instanceof IEnumeration) { + signal = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 0)), safeArgsAt(args, 1)); + member = QtMethodReference.parse(call, ASTUtil.getReceiverType(call), safeArgsAt(args, 2)); + } + + // Merge non-null signal and slot to return a list of 0, 1, or 2 elements. + if (signal == null) { + if (member == null) + return null; + return Collections.singletonList(member); + } + + if (member == null) + return Collections.singletonList(signal); + + return Arrays.asList(signal, member); + } + + private static Collection getReferencesInDisconnect(ICPPFunction function, IASTFunctionCallExpression call) { + if (function == null) + return null; + + // There are 4 overloads of QObject::disconnect (Qt 4.8.4). They can be distinguished by examining + // the type of the second parameter. The number of parameters is used to disambiguate one conflict. + // disconnect( , const char *, , ) && 4 params + // disconnect( , QMetaMethod&, , ) + // disconnect( , const QObject *, ) + // disconnect( , const char * ) && 2 params + ICPPParameter[] params = function.getParameters(); + if (params.length < 2) + return null; + + IASTInitializerClause[] args = call.getArguments(); + IType type1 = ASTUtil.getBaseType(params[1].getType()); + + // static bool disconnect( const QObject *sender, const QMetaMethod &signal, + // const QObject *receiver, const QMetaMethod &member ); + if (QtKeywords.isQMetaMethod(type1)) { + // QMetaMethod cannot be statically analyzed. + return Collections.emptyList(); + } + + // Otherwise find the signal and member parameters based on the overload. + IASTName signal = null; + IASTName member = null; + + if (type1 instanceof IBasicType && ( (IBasicType)type1 ).getKind() == IBasicType.Kind.eChar ) { + switch(params.length) { + // static bool disconnect( const QObject *sender, const char *signal, + // const QObject *receiver, const char *member ); + case 4: + signal = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 0)), safeArgsAt(args, 1)); + member = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 2)), safeArgsAt(args, 3)); + break; + + // inline bool disconnect( const QObject *receiver, const char *member = 0 ); + case 2: + member = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 0)), safeArgsAt(args, 1)); + break; + } + } + + // inline bool disconnect( const char *signal = 0, + // const QObject *receiver = 0, const char *member = 0 ); + else if (QtKeywords.isQObject(type1)) { + ICPPClassType recvr = ASTUtil.getReceiverType(call); + signal = QtMethodReference.parse(call, recvr, safeArgsAt(args, 0)); + member = QtMethodReference.parse(call, ASTUtil.getBaseType(safeArgsAt(args, 1)), safeArgsAt(args, 2)); + } + + // Merge non-null signal and slot to return a list of 0, 1, or 2 elements. + if (signal == null) { + if (member == null) + return null; + return Collections.singletonList(member); + } + + if (member == null) + return Collections.singletonList(signal); + + return Arrays.asList(signal, member); + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtMethodReference.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtMethodReference.java new file mode 100644 index 00000000000..98d670abc27 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtMethodReference.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2014 QNX Software Systems 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 + */ +package org.eclipse.cdt.internal.qt.core; + +import java.util.regex.Matcher; + +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.IType; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; +import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics; +import org.eclipse.cdt.internal.qt.core.pdom.ASTNameReference; +import org.eclipse.cdt.internal.qt.core.pdom.QtASTImageLocation; +import org.eclipse.cdt.qt.core.QtKeywords; + +/** + * Qt signals and slots are referenced using the SIGNAL and SLOT macros. The expansion + * parameter is the signature of the signal or slot and they are associated with a type. + * This utility class is used to convert from these AST nodes to an IASTName that can be + * used as a reference to the IBinding for the C++ method. + */ +@SuppressWarnings("restriction") +public class QtMethodReference extends ASTNameReference { + + public static enum Type { + Signal("sender", "SIGNAL", "signal"), + Slot("receiver", "SLOT", "member"); + + public final String roleName; + public final String macroName; + public final String paramName; + + public boolean matches(Type other) { + if (other == null) + return false; + + // The signal parameter must be a SIGNAL, but the slot could be a + // SLOT or a SIGNAL. + return this == Signal ? other == Signal : true; + } + + /** + * Return the type of method reference within the expansion of the given macro name. + */ + public static Type from(IASTName name) { + String nameStr = String.valueOf(name); + if (QtKeywords.SIGNAL.equals(nameStr)) + return Signal; + if (QtKeywords.SLOT.equals(nameStr)) + return Slot; + return null; + } + + private Type(String roleName, String macroName, String paramName) { + this.roleName = roleName; + this.macroName = macroName; + this.paramName = paramName; + } + } + + private final Type type; + private final ICPPClassType cls; + private final String expansionParam; + + private QtMethodReference(Type type, ICPPClassType cls, IASTName macroRefName, String expansionParam, IASTFileLocation location) { + super(macroRefName, location); + + this.type = type; + this.cls = cls; + this.expansionParam = expansionParam; + } + + /** + * Look for SIGNAL or SLOT macro expansions at the location of the given node. Return the + * QMethod reference is an expansion is found and null otherwise. + *

+ * QMetaMethod references cannot be statically resolved so null will be returned in this case. + */ + public static QtMethodReference parse(IASTNode parent, IType cppType, IASTNode arg) { + if (!(cppType instanceof ICPPClassType) || arg == null) + return null; + ICPPClassType cls = (ICPPClassType) cppType; + + // Look for a SIGNAL or SLOT expansion as this location. + Type type = null; + IASTName macroReferenceName = null; + for(IASTNodeLocation location : arg.getNodeLocations()) { + if (!(location instanceof IASTMacroExpansionLocation)) + continue; + + IASTPreprocessorMacroExpansion expansion = ((IASTMacroExpansionLocation) location).getExpansion(); + macroReferenceName = expansion.getMacroReference(); + IASTPreprocessorMacroDefinition macroDefn = expansion.getMacroDefinition(); + + type = Type.from(macroDefn.getName()); + if (type != null) + break; + } + + // There is nothing to do if the expected type of expansion is not found. + if (macroReferenceName == null || type == null) + return null; + + // This check will miss cases like: + // #define MY_SIG1 SIGNAL + // #define MY_SIG2(s) SIGNAL(s) + // #define MY_SIG3(s) SIGNAL(signal()) + // connect( &a, MY_SIG1(signal()), ... + // connect( &a, MY_SIG2(signal()), ... + // connect( &a, MY_SIG2, ... + // This could be improved by adding tests when arg represents a macro expansion. However, I'm + // not sure if we would be able to follow the more complicated case of macros that call functions + // that use the SIGNAL macro. For now I've implemented the simpler check of forcing the call to + // use the SIGNAL/SLOT macro directly. + String raw = arg.getRawSignature(); + Matcher m = ASTUtil.Regex_MacroExpansion.matcher( raw ); + if( ! m.matches() ) + return null; + + // Get the argument to the SIGNAL/SLOT macro and the offset/length of that argument within the + // complete function argument. E.g., with this argument to QObject::connect + // SIGNAL( signal(int) ) + // the values are + // expansionArgs: "signal(int)" + // expansionOffset: 8 + // expansionLength: 11 + String expansionArgs = m.group( 2 ); + int expansionOffset = m.start( 2 ); + int expansionLength = m.end( 2 ) - expansionOffset; + + IASTFileLocation location = new QtASTImageLocation(macroReferenceName.getFileLocation(), expansionOffset, expansionLength); + return new QtMethodReference(type, cls, macroReferenceName, expansionArgs, location); + } + + public Type getType() { + return type; + } + + @Override + public String getRawSignature() { + return expansionParam; + } + + @Override + public char[] toCharArray() { + return expansionParam.toCharArray(); + } + + @Override + public IBinding resolveBinding() { + if (binding != null) + return binding; + + // Qt method references return the C++ method that is being referenced in the SIGNAL or + // SLOT macro expansion. + String methodName = expansionParam; + int paren = methodName.indexOf('('); + if (paren > 0) + methodName = methodName.substring(0, paren); + IBinding[] methods = CPPSemantics.findBindings(cls.getCompositeScope(), methodName.trim(), false); + + // TODO find the one binding that matches the parameter of the macro expansion + // 1) Normalize expansionParam + // 2) Use it to filter the matching methods + binding = methods.length > 0 ? methods[0] : null; + return binding; + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java deleted file mode 100644 index dac7389bc04..00000000000 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2013 QNX Software Systems 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 - */ -package org.eclipse.cdt.internal.qt.core; - -import java.util.regex.Matcher; - -import org.eclipse.cdt.core.dom.ast.IASTName; -import org.eclipse.cdt.core.dom.ast.IASTNode; -import org.eclipse.cdt.core.dom.ast.IBinding; -import org.eclipse.cdt.qt.core.QtKeywords; - -public class QtSignalSlotReference -{ - public static enum Type - { - Signal( "sender", "SIGNAL", "signal" ), - Slot ( "receiver", "SLOT", "slot" ); - - public final String roleName; - public final String macroName; - public final String paramName; - - public boolean matches( Type other ) - { - if( other == null ) - return false; - - // The signal parameter must be a SIGNAL, but the slot could be a - // SLOT or a SIGNAL. - return this == Signal ? other == Signal : true; - } - - private Type( String roleName, String macroName, String paramName ) - { - this.roleName = roleName; - this.macroName = macroName; - this.paramName = paramName; - } - } - - public final IASTNode parent; - public final IASTNode node; - public final Type type; - public final String signature; - public final int offset; - public final int length; - - private QtSignalSlotReference( IASTNode parent, IASTNode node, Type type, String signature, int offset, int length ) - { - this.parent = parent; - this.node = node; - this.type = type; - this.signature = signature; - this.offset = offset; - this.length = length; - } - - public IASTName createName( IBinding binding ) - { - return new QtSignalSlotReferenceName( parent, node, signature, offset, length, binding ); - } - - public static QtSignalSlotReference parse( IASTNode parent, IASTNode arg ) - { - // This check will miss cases like: - // #define MY_SIG1 SIGNAL - // #define MY_SIG2(s) SIGNAL(s) - // #define MY_SIG3(s) SIGNAL(signal()) - // connect( &a, MY_SIG1(signal()), ... - // connect( &a, MY_SIG2(signal()), ... - // connect( &a, MY_SIG2, ... - // This could be improved by adding tests when arg represents a macro expansion. However, I'm - // not sure if we would be able to follow the more complicated case of macros that call functions - // that use the SIGNAL macro. For now I've implemented the simpler check of forcing the call to - // use the SIGNAL/SLOT macro directly. - String raw = arg.getRawSignature(); - Matcher m = ASTUtil.Regex_SignalSlotExpansion.matcher( raw ); - if( ! m.matches() ) - return null; - - Type type; - String macroName = m.group( 1 ); - if( QtKeywords.SIGNAL.equals( macroName ) ) - type = Type.Signal; - else if( QtKeywords.SLOT.equals( macroName ) ) - type = Type.Slot; - else - return null; - - // Get the argument to the SIGNAL/SLOT macro and the offset/length of that argument within the - // complete function argument. E.g., with this argument to QObject::connect - // SIGNAL( signal(int) ) - // the values are - // expansionArgs: "signal(int)" - // expansionOffset: 8 - // expansionLength: 11 - String expansionArgs = m.group( 2 ); - int expansionOffset = m.start( 2 ); - int expansionLength = m.end( 2 ) - expansionOffset; - - return new QtSignalSlotReference( parent, arg, type, expansionArgs, expansionOffset, expansionLength ); - } -} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java deleted file mode 100644 index 5b3679638b5..00000000000 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2013 QNX Software Systems 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 - */ -package org.eclipse.cdt.internal.qt.core; - -import org.eclipse.cdt.core.dom.ast.IASTFileLocation; -import org.eclipse.cdt.core.dom.ast.IASTImageLocation; -import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; - -/** - * The location of the signal/slot reference is stored as the location of the parent - * macro expansion + an offset, which is the number of characters between the start - * of the expansion and the start of the argument (including whitespace). E.g. in, - * - *

- * SIGNAL( signal1( int ) )
- * ^       ^            ^ c: end of reference name
- * |       +------------- b: start of reference name
- * +--------------------- a: start of macro expansion
- * 
- * - * The offset is b - a and length is c - a. This means that the result of 'Find - * References' will highlight just "signal( int )". - * - * @see QtSignalSlotReferenceName - */ -public class QtSignalSlotReferenceLocation implements IASTImageLocation { - - private final IASTFileLocation referenceLocation; - private final int offset; - private final int length; - - public QtSignalSlotReferenceLocation(IASTFileLocation referenceLocation, int offset, int length) { - this.referenceLocation = referenceLocation; - this.offset = offset; - this.length = length; - } - - @Override - public int getLocationKind() { - return IASTImageLocation.ARGUMENT_TO_MACRO_EXPANSION; - } - - @Override - public int getNodeOffset() { - return referenceLocation.getNodeOffset() + offset; - } - - @Override - public int getNodeLength() { - return length; - } - - @Override - public String getFileName() { - return referenceLocation.getFileName(); - } - - @Override - public IASTFileLocation asFileLocation() { - return referenceLocation; - } - - @Override - public int getEndingLineNumber() { - return referenceLocation.getEndingLineNumber(); - } - - @Override - public int getStartingLineNumber() { - return referenceLocation.getStartingLineNumber(); - } - - @Override - public IASTPreprocessorIncludeStatement getContextInclusionStatement() { - return referenceLocation.getContextInclusionStatement(); - } -} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java deleted file mode 100644 index 46f7259bf9f..00000000000 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2013 QNX Software Systems 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 - */ -package org.eclipse.cdt.internal.qt.core; - -import org.eclipse.cdt.core.dom.ILinkage; -import org.eclipse.cdt.core.dom.ast.ASTNodeProperty; -import org.eclipse.cdt.core.dom.ast.ASTVisitor; -import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException; -import org.eclipse.cdt.core.dom.ast.IASTCompletionContext; -import org.eclipse.cdt.core.dom.ast.IASTFileLocation; -import org.eclipse.cdt.core.dom.ast.IASTImageLocation; -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.IASTNodeLocation; -import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; -import org.eclipse.cdt.core.dom.ast.IBinding; -import org.eclipse.cdt.core.parser.IToken; - -/** - * Signals are connected to slots by referencing them within an expansion of SIGNAL - * or SLOT. E.g., - * - *
- * class A : public QObject
- * {
- *     Q_SIGNAL void signal1( int );
- *     Q_SLOT   void slot1();
- * };
- * A a;
- * QObject::connect( &a, SIGNAL( signal1( int ) ), &a, SLOT( slot1() ) );
- * 
- * - * The goal is for 'Find References' on the function declarations to find the references - * in the macro expansions. The PDOM stores references as a linked list from the binding - * for the function. - * - * This class represents the name within the expansion, i.e., "signal1( int )" within - * "SIGNAL( signal1( int ) )" and "slot1()" within "SLOT( slot1() )". - */ -public class QtSignalSlotReferenceName implements IASTName { - - private final IASTNode referenceNode; - private final String argument; - private final IBinding binding; - private final IASTImageLocation location; - - private IASTNode parent; - private ASTNodeProperty propertyInParent; - - public QtSignalSlotReferenceName(IASTNode parent, IASTNode referenceNode, String argument, int offset, int length, IBinding binding) { - this.parent = parent; - this.referenceNode = referenceNode; - this.argument = argument; - this.binding = binding; - - IASTFileLocation referenceLocation = referenceNode.getFileLocation(); - this.location - = referenceLocation == null - ? null - : new QtSignalSlotReferenceLocation(referenceLocation, offset, length); - } - - @Override - public char[] toCharArray() { - return argument.toCharArray(); - } - - @Override - public char[] getSimpleID() { - return toCharArray(); - } - - @Override - public char[] getLookupKey() { - return toCharArray(); - } - - @Override - public IASTTranslationUnit getTranslationUnit() { - return referenceNode.getTranslationUnit(); - } - - @Override - public IASTFileLocation getFileLocation() { - return getImageLocation(); - } - - @Override - public IASTNodeLocation[] getNodeLocations() { - // The javadoc says that locations that are completely enclosed within a - // macro expansion return only the location of that expansion. - return referenceNode.getNodeLocations(); - } - - @Override - public String getContainingFilename() { - return referenceNode.getContainingFilename(); - } - - @Override - public boolean isPartOfTranslationUnitFile() { - return referenceNode.isPartOfTranslationUnitFile(); - } - - @Override - public IASTNode[] getChildren() { - return new IASTNode[0]; - } - - @Override - public IASTNode getParent() { - return parent; - } - - @Override - public void setParent(IASTNode node) { - this.parent = node; - } - - @Override - public ASTNodeProperty getPropertyInParent() { - return propertyInParent; - } - - @Override - public void setPropertyInParent(ASTNodeProperty property) { - propertyInParent = property; - } - - @Override - public boolean accept(ASTVisitor visitor) { - // The signal/slot reference has nothing to visit. It will have been - // reached by the reference node, so we can't visit that, and there is - // nothing else. - return false; - } - - @Override - public String getRawSignature() { - // The raw signature of the reference is the text of the argument. - return argument; - } - - @Override - public boolean contains(IASTNode node) { - // There aren't any nodes contained within the signal/slot reference. - return false; - } - - @Override - public IToken getLeadingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException { - // The parent is the macro reference name, and this is the entire - // content of the arguments. Since there is nothing between these, there - // will not be any leading syntax. - return null; - } - - @Override - public IToken getTrailingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException { - // The parent is the macro reference name, and this is the entire - // content of the arguments. Since there is nothing between these, there - // will not be any leading syntax. - return null; - } - - @Override - public IToken getSyntax() throws ExpansionOverlapsBoundaryException { - // This reference to the signal/slot function is fully contained within - // a preprocessor node, which does not support syntax. - throw new UnsupportedOperationException(); - } - - @Override - public boolean isFrozen() { - return referenceNode.isFrozen(); - } - - @Override - public boolean isActive() { - return referenceNode.isActive(); - } - - @Override - public int getRoleOfName(boolean allowResolution) { - return IASTNameOwner.r_reference; - } - - @Override - public boolean isDeclaration() { - return false; - } - - @Override - public boolean isReference() { - return true; - } - - @Override - public boolean isDefinition() { - return false; - } - - @Override - public IBinding getBinding() { - return binding; - } - - @Override - public IBinding resolveBinding() { - return getBinding(); - } - - @Override - public IASTCompletionContext getCompletionContext() { - // Signal/slot references are fully contained within a macro expansion, - // so there is no completion context. - return null; - } - - @Override - public ILinkage getLinkage() { - return referenceNode instanceof IASTName ? ((IASTName) referenceNode).getLinkage() : null; - } - - @Override - public IASTImageLocation getImageLocation() { - return location; - } - - @Override - public IASTName getLastName() { - // Signal/slot references are not qualified, so return itself. - return this; - } - - @Override - public boolean isQualified() { - return false; - } - - @Override - public IASTName copy() { - // Signal/slot references are preprocessor nodes, so they don't support - // copying. - throw new UnsupportedOperationException(); - } - - @Override - public IASTName copy(CopyStyle style) { - // Signal/slot references are preprocessor nodes, so they don't support - // copying. - throw new UnsupportedOperationException(); - } - - @Override - public IASTNode getOriginalNode() { - return this; - } - - @Override - public void setBinding(IBinding binding) { - // Signal/slot references find their binding on instantiation, they - // never allow it to be replaced. - throw new UnsupportedOperationException(); - } - - @Override - public IBinding getPreBinding() { - return getBinding(); - } - - @Override - public IBinding resolvePreBinding() { - return getBinding(); - } - - @Override - public String toString() { - return "QtSignalSlotReference(" + new String(toCharArray()) + ')'; - } -} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTDelegatedName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTDelegatedName.java index 271c3f5d3d5..3e0cb5b7a1f 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTDelegatedName.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTDelegatedName.java @@ -133,11 +133,6 @@ public abstract class ASTDelegatedName implements IASTName { return delegate.getOriginalNode(); } - @Override - public char[] getSimpleID() { - return delegate.getSimpleID(); - } - @Override public boolean isDeclaration() { return delegate.isDeclaration(); @@ -158,9 +153,19 @@ public abstract class ASTDelegatedName implements IASTName { return delegate.toCharArray(); } + @Override + public char[] getSimpleID() { + return toCharArray(); + } + + @Override + public char[] getLookupKey() { + return toCharArray(); + } + @Override public IBinding getBinding() { - return null; + return binding; } @Override @@ -208,11 +213,6 @@ public abstract class ASTDelegatedName implements IASTName { this.binding = binding; } - @Override - public char[] getLookupKey() { - return delegate.getLookupKey(); - } - @Override public IBinding getPreBinding() { return binding; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTNameReference.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTNameReference.java index 52d511a8337..5903584f045 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTNameReference.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/ASTNameReference.java @@ -74,12 +74,9 @@ public class ASTNameReference extends ASTDelegatedName { @Override public IBinding resolveBinding() { - return delegate.resolveBinding(); - } - - @Override - public IBinding getBinding() { - return delegate.getBinding(); + if (binding == null) + binding = delegate.resolveBinding(); + return binding; } @Override diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/QtASTVisitor.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/QtASTVisitor.java index 4273ef4d6b8..c52503bcfdb 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/QtASTVisitor.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/pdom/QtASTVisitor.java @@ -8,6 +8,7 @@ package org.eclipse.cdt.internal.qt.core.pdom; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,8 +20,9 @@ import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; -import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; @@ -35,6 +37,7 @@ import org.eclipse.cdt.core.index.IIndexSymbols; import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics; import org.eclipse.cdt.internal.core.parser.scanner.LocationMap; +import org.eclipse.cdt.internal.qt.core.QtFunctionCall; import org.eclipse.cdt.internal.qt.core.QtMethodUtil; import org.eclipse.cdt.internal.qt.core.index.QProperty; import org.eclipse.cdt.qt.core.QtKeywords; @@ -92,6 +95,7 @@ public class QtASTVisitor extends ASTVisitor { public QtASTVisitor(IIndexSymbols symbols, LocationMap locationMap) { shouldVisitDeclSpecifiers = true; + shouldVisitExpressions = true; this.symbols = symbols; this.locationMap = locationMap; @@ -114,6 +118,23 @@ public class QtASTVisitor extends ASTVisitor { return super.visit(declSpec); } + @Override + public int visit(IASTExpression expr) { + if (expr instanceof IASTFunctionCallExpression) { + Collection refs = QtFunctionCall.getReferences((IASTFunctionCallExpression) expr); + if (refs != null) + for (IASTName ref : refs) { + IASTFileLocation nameLoc = ref.getFileLocation(); + if (nameLoc != null) { + IASTPreprocessorIncludeStatement owner = nameLoc.getContextInclusionStatement(); + symbols.add(owner, ref, null); + } + } + } + + return super.visit(expr); + } + private boolean isQObject(ICPPASTCompositeTypeSpecifier spec, IASTPreprocessorMacroExpansion[] expansions) { // The class definition must contain a Q_OBJECT expansion. @@ -434,21 +455,4 @@ public class QtASTVisitor extends ASTVisitor { node = node.getParent(); return node instanceof IASTSimpleDeclaration ? (IASTSimpleDeclaration) node : null; } - - /** - * Return the node's IASTName if it is a function and null otherwise. - */ - private static IASTName getFunctionName(IASTDeclaration decl) { - IASTSimpleDeclaration simpleDecl = getSimpleDecl(decl); - if (simpleDecl == null) - return null; - - // Only functions can be signals or slots. Return the name of the first declarator - // that is a function. - for( IASTDeclarator decltor : simpleDecl.getDeclarators()) - if (decltor instanceof IASTFunctionDeclarator) - return decltor.getName(); - - return null; - } } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java index 86e935beb6f..20f0e368df5 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java @@ -10,6 +10,8 @@ package org.eclipse.cdt.qt.core; import org.eclipse.cdt.core.dom.ast.DOMException; import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.IType; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction; /** @@ -41,27 +43,38 @@ public class QtKeywords { public static final String SLOT = "SLOT"; public static final String SLOTS = "slots"; + /** + * Returns true if the argument type is for Qt's QObject class and false otherwise. + */ + public static boolean isQObject(IType type) { + if (!(type instanceof ICPPClassType)) + return false; + + ICPPClassType clsType = (ICPPClassType)type; + return QtKeywords.QOBJECT.equals(clsType.getName()); + } + + /** + * Returns true if the argument type is for Qt's QMetaMethod class and false otherwise. + */ + public static boolean isQMetaMethod(IType type) { + if (!(type instanceof ICPPClassType)) + return false; + + ICPPClassType clsType = (ICPPClassType)type; + return QMETAMETHOD.equals(clsType.getName()); + } + /** * Returns true if the argument binding is for the QObject::connect function * and false otherwise. */ public static boolean is_QObject_connect(IBinding binding) { - if (binding == null) - return false; - - // IBinding#getAdapter returns null when binding is an instance of - // PDOMCPPMethod. - if (!(binding instanceof ICPPFunction)) - return false; - - try { - String[] qualName = ((ICPPFunction) binding).getQualifiedName(); - return qualName.length == 2 - && QOBJECT.equals(qualName[0]) - && CONNECT.equals(qualName[1]); - } catch (DOMException e) { - return false; - } + String[] qualName = getFunctionQualifiedName(binding); + return qualName != null + && qualName.length == 2 + && QOBJECT.equals(qualName[0]) + && CONNECT.equals(qualName[1]); } /** @@ -69,21 +82,22 @@ public class QtKeywords { * and false otherwise. */ public static boolean is_QObject_disconnect(IBinding binding) { - if (binding == null) - return false; + String[] qualName = getFunctionQualifiedName(binding); + return qualName != null + && qualName.length == 2 + && QOBJECT.equals(qualName[0]) + && DISCONNECT.equals(qualName[1]); + } + private static String[] getFunctionQualifiedName(IBinding binding) { // IBinding#getAdapter returns null when binding is an instance of // PDOMCPPMethod. - if (!(binding instanceof ICPPFunction)) - return false; - - try { - String[] qualName = ((ICPPFunction) binding).getQualifiedName(); - return qualName.length == 2 - && QOBJECT.equals(qualName[0]) - && DISCONNECT.equals(qualName[1]); - } catch (DOMException e) { - return false; - } + if (binding instanceof ICPPFunction) + try { + return ((ICPPFunction) binding).getQualifiedName(); + } catch (DOMException e) { + QtPlugin.log(e); + } + return null; } } diff --git a/qt/org.eclipse.cdt.qt.tests/META-INF/MANIFEST.MF b/qt/org.eclipse.cdt.qt.tests/META-INF/MANIFEST.MF index a6110d5794a..4ceb2632e98 100644 --- a/qt/org.eclipse.cdt.qt.tests/META-INF/MANIFEST.MF +++ b/qt/org.eclipse.cdt.qt.tests/META-INF/MANIFEST.MF @@ -14,7 +14,10 @@ Require-Bundle: org.eclipse.ui, org.eclipse.cdt.codan.core, org.eclipse.cdt.qt.ui, org.eclipse.jface.text, - org.eclipse.cdt.ui + org.eclipse.cdt.ui, + org.eclipse.ui.ide, + org.eclipse.ui.editors, + org.eclipse.search Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-Vendor: %vendorName diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/BaseQtTestCase.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/BaseQtTestCase.java index 2c023b29ba2..8bc1d1b755f 100644 --- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/BaseQtTestCase.java +++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/BaseQtTestCase.java @@ -88,14 +88,23 @@ public class BaseQtTestCase extends BaseTestCase { // #define SIGNAL(a) qFlagLocation("2"#a) // #define QML_DECLARE_TYPEINFO( T, F ) template <> struct QDeclarativeTypeInfo { enum { H = F }; }; // enum { QML_HAS_ATTACHED_PROPERTIES = 0x01 }; + // namespace Qt { enum ConnectionType { AutoConnection }; } + // class QMetaMethod { }; // class QObject // { // Q_OBJECT // Q_SIGNAL void destroyed( QObject * ); // public: - // static bool connect( QObject *, const char *, QObject *, const char * ); + // static bool connect( const QObject *, const char *, const QObject *, const char *, Qt::ConnectionType = Qt::AutoConnection ); + // static bool connect( const QObject *, const QMetaMethod &, const QObject *, const QMetaMethod &, Qt::ConnectionType = Qt::AutoConnection ); + // bool connect( const QObject *, const char *, const char *, Qt::ConnectionType = Qt::AutoConnection ); + // static bool disconnect( const QObject *, const char *, const QObject *, const char * ); + // static bool disconnect( const QObject *, const QMetaMethod &, const QObject *, const QMetaMethod & ); + // bool disconnect( const char * = 0, const QObject * = 0, const char * = 0 ); + // bool disconnect( const QObject *, const char * = 0 ); // }; // class QString { public: QString( const char * ch ); }; + // template class QList { }; // template int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName); // template int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName); // template int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMinor, const char *qmlName, const QString& reason); diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QObjectTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QObjectTests.java index 5a3dfcd8071..9a13400cb19 100644 --- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QObjectTests.java +++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QObjectTests.java @@ -15,6 +15,9 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import org.eclipse.cdt.core.index.IIndexBinding; +import org.eclipse.cdt.core.index.IIndexName; +import org.eclipse.cdt.core.index.IndexFilter; import org.eclipse.cdt.qt.core.index.IQEnum; import org.eclipse.cdt.qt.core.index.IQMethod; import org.eclipse.cdt.qt.core.index.IQObject; @@ -57,8 +60,6 @@ public class QObjectTests extends BaseQtTestCase { } // #include "junit-QObject.hh" - // template class QList {}; - // class QString {}; // class Q0 : public QObject // { // Q_OBJECT @@ -137,7 +138,6 @@ public class QObjectTests extends BaseQtTestCase { } // #include "junit-QObject.hh" - // template class QList {}; // class Q : public QObject // { // Q_OBJECT @@ -452,9 +452,14 @@ public class QObjectTests extends BaseQtTestCase { assertFalse(i.hasNext()); } + private static void assert_checkQMethod(IQMethod method, IQObject expectedOwner, String expectedName, IQMethod.Kind expectedKind, Long expectedRevision) throws Exception { + assertEquals(expectedKind, method.getKind()); + assertEquals(expectedName, method.getName()); + assertSame(method.getName(), expectedOwner, method.getOwner()); + assertEquals(expectedRevision, method.getRevision()); + } + // #include "junit-QObject.hh" - // template class QList {}; - // class QString {}; // class Q : public QObject // { // Q_OBJECT @@ -512,10 +517,173 @@ public class QObjectTests extends BaseQtTestCase { } } - private static void assert_checkQMethod(IQMethod method, IQObject expectedOwner, String expectedName, IQMethod.Kind expectedKind, Long expectedRevision) throws Exception { - assertEquals(expectedKind, method.getKind()); - assertEquals(expectedName, method.getName()); - assertSame(method.getName(), expectedOwner, method.getOwner()); - assertEquals(expectedRevision, method.getRevision()); + // #include "junit-QObject.hh" + // class QUnrelated : public QObject { Q_OBJECT }; + // class Q : public QObject + // { + // Q_OBJECT + // Q_SIGNAL void signal1(); + // Q_SLOT void slot1(); + // + // void f1(); + // void f2() + // { + // Q q, *q_sender = this, *q_receiver = this; + // QUnrelated *q_unrelated; + // QMetaMethod meta = q.metaObject()->method( 0 ); + // + // // static bool connect(const QObject *sender, const char *signal, + // // const QObject *receiver, const char *member, + // // Qt::ConnectionType = Qt::AutoConnection); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()), Qt::AutoConnection ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()), Qt::AutoConnection ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()), Qt::AutoConnection ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()), Qt::AutoConnection ); + // + // // static bool connect(const QObject *sender, const QMetaMethod &signal, + // // const QObject *receiver, const QMetaMethod &method, + // // Qt::ConnectionType type = Qt::AutoConnection); + // QObject::connect( q_sender, meta, q_receiver, meta ); + // QObject::connect( q_sender, meta, q_receiver, meta, Qt::AutoConnection ); + // q_unrelated->connect( q_sender, meta, q_receiver, meta ); + // q_unrelated->connect( q_sender, meta, q_receiver, meta, Qt::AutoConnection ); + // + // // inline bool connect(const QObject *sender, const char *signal, + // // const char *member, + // // Qt::ConnectionType type = Qt::AutoConnection) const; + // q_receiver->connect( q_sender, SIGNAL(signal1()), SIGNAL(signal1()) ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SLOT(slot1()) ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SIGNAL(signal1()), Qt::AutoConnection ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SLOT(slot1()), Qt::AutoConnection ); + // + // // static bool disconnect(const QObject *sender, const char *signal, + // // const QObject *receiver, const char *member); + // QObject::disconnect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // QObject::disconnect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_unrelated->disconnect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_unrelated->disconnect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // + // // static bool disconnect(const QObject *sender, const QMetaMethod &signal, + // // const QObject *receiver, const QMetaMethod &member); + // QObject::disconnect( q_sender, meta, q_receiver, meta ); + // q_unrelated->disconnect( q_sender, meta, q_receiver, meta ); + // + // // inline bool disconnect(const char *signal = 0, + // // const QObject *receiver = 0, const char *member = 0); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver ); + // q_sender->disconnect( SIGNAL(signal1()) ); + // q_sender->disconnect(); + // + // // inline bool disconnect(const QObject *receiver, const char *member = 0); + // q_sender->disconnect( q_receiver, SIGNAL(signal1()) ); + // q_sender->disconnect( q_receiver, SLOT(slot1()) ); + // q_sender->disconnect( q_receiver ); + // } + // }; + // // This is exactly the same as the inline function body. It is duplicated here because of + // // an old bug where the function definitions were not properly indexed (for Qt). + // void Q::f1() + // { + // Q q, *q_sender = this, *q_receiver = this; + // QUnrelated *q_unrelated; + // QMetaMethod meta = q.metaObject()->method( 0 ); + // + // // static bool connect(const QObject *sender, const char *signal, + // // const QObject *receiver, const char *member, + // // Qt::ConnectionType = Qt::AutoConnection); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()), Qt::AutoConnection ); + // QObject::connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()), Qt::AutoConnection ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()), Qt::AutoConnection ); + // q_unrelated->connect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()), Qt::AutoConnection ); + // + // // static bool connect(const QObject *sender, const QMetaMethod &signal, + // // const QObject *receiver, const QMetaMethod &method, + // // Qt::ConnectionType type = Qt::AutoConnection); + // QObject::connect( q_sender, meta, q_receiver, meta ); + // QObject::connect( q_sender, meta, q_receiver, meta, Qt::AutoConnection ); + // q_unrelated->connect( q_sender, meta, q_receiver, meta ); + // q_unrelated->connect( q_sender, meta, q_receiver, meta, Qt::AutoConnection ); + // + // // inline bool connect(const QObject *sender, const char *signal, + // // const char *member, + // // Qt::ConnectionType type = Qt::AutoConnection) const; + // q_receiver->connect( q_sender, SIGNAL(signal1()), SIGNAL(signal1()) ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SLOT(slot1()) ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SIGNAL(signal1()), Qt::AutoConnection ); + // q_receiver->connect( q_sender, SIGNAL(signal1()), SLOT(slot1()), Qt::AutoConnection ); + // + // // static bool disconnect(const QObject *sender, const char *signal, + // // const QObject *receiver, const char *member); + // QObject::disconnect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // QObject::disconnect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_unrelated->disconnect( q_sender, SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_unrelated->disconnect( q_sender, SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // + // // static bool disconnect(const QObject *sender, const QMetaMethod &signal, + // // const QObject *receiver, const QMetaMethod &member); + // QObject::disconnect( q_sender, meta, q_receiver, meta ); + // q_unrelated->disconnect( q_sender, meta, q_receiver, meta ); + // + // // inline bool disconnect(const char *signal = 0, + // // const QObject *receiver = 0, const char *member = 0); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver, SIGNAL(signal1()) ); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver, SLOT(slot1()) ); + // q_sender->disconnect( SIGNAL(signal1()), q_receiver ); + // q_sender->disconnect( SIGNAL(signal1()) ); + // q_sender->disconnect(); + // + // // inline bool disconnect(const QObject *receiver, const char *member = 0); + // q_sender->disconnect( q_receiver, SIGNAL(signal1()) ); + // q_sender->disconnect( q_receiver, SLOT(slot1()) ); + // q_sender->disconnect( q_receiver ); + // } + public void testSignalSlotReferences() throws Exception { + loadComment("sig_slot_refs.hh"); + waitForIndexer(fCProject); + + // References are from the function's IBinding. + assertNotNull(fIndex); + fIndex.acquireReadLock(); + try { + char[][] Q_signal1_qn = new char[][]{ "Q".toCharArray(), "signal1".toCharArray() }; + IIndexBinding[] Q_signal1s = fIndex.findBindings(Q_signal1_qn, IndexFilter.CPP_DECLARED_OR_IMPLICIT, npm()); + assertNotNull(Q_signal1s); + assertEquals(1, Q_signal1s.length); + IIndexBinding Q_signal1 = Q_signal1s[0]; + assertNotNull(Q_signal1); + + char[][] Q_slot1_qn = new char[][]{ "Q".toCharArray(), "slot1".toCharArray() }; + IIndexBinding[] Q_slot1s = fIndex.findBindings(Q_slot1_qn, IndexFilter.CPP_DECLARED_OR_IMPLICIT, npm()); + assertNotNull(Q_slot1s); + assertEquals(1, Q_slot1s.length); + IIndexBinding Q_slot1 = Q_slot1s[0]; + assertNotNull(Q_slot1); + + // Each valid variant of the connect function call should have one reference + // in the inline function (f2) and one reference in the function with a separate + // definition (f1). + int expectedSignalRefs = 2 * 30; + int expectedSlotRefs = 2 * 10; + + IIndexName[] signalRefs = fIndex.findReferences(Q_signal1); + assertNotNull(signalRefs); + assertEquals(expectedSignalRefs, signalRefs.length); + + IIndexName[] slotRefs = fIndex.findReferences(Q_slot1); + assertNotNull(slotRefs); + assertEquals(expectedSlotRefs, slotRefs.length); + } finally { + fIndex.releaseReadLock(); + } } } diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtRegressionTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtRegressionTests.java index bf168b7c588..cc8b2e619df 100644 --- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtRegressionTests.java +++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtRegressionTests.java @@ -14,10 +14,27 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.eclipse.cdt.core.index.IIndex; +import org.eclipse.cdt.internal.core.model.ext.SourceRange; +import org.eclipse.cdt.internal.ui.editor.CEditor; +import org.eclipse.cdt.internal.ui.search.CSearchResult; +import org.eclipse.cdt.internal.ui.search.CSearchTextSelectionQuery; import org.eclipse.cdt.qt.core.index.IQMethod; import org.eclipse.cdt.qt.core.index.IQObject; import org.eclipse.cdt.qt.core.index.QtIndex; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.search.ui.ISearchResult; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +@SuppressWarnings("restriction") public class QtRegressionTests extends BaseQtTestCase { private static Map> buildExpectedMap(String mocOutput) { @@ -334,4 +351,70 @@ public class QtRegressionTests extends BaseQtTestCase { assertTrue("unexpected slot " + method.getName(), expected.remove(method.getName())); assertEquals("missing slots " + expected.toString(), 0, expected.size()); } + + // #include "junit-QObject.hh" + // class Q : public QObject + // { + // Q_OBJECT + // Q_SIGNAL void signal1(); + // Q_SLOT void slot1(); + // void function() + // { + // signal1(); + // QObject::connect( + // this, SIGNAL( signal1() ), + // this, SLOT( slot1() ) ); + // } + // }; + public void testBug424499_FindQMethodReferences() throws Exception { + String filename = "signalRefs.hh"; + loadComment(filename); + waitForIndexer(fCProject); + + // The search query uses the ASTProvider which relies on the target translation unit being + // loaded in a CEditor. The following opens a CEditor for the test file so that it will + // be the one used by the ASTProvider. + IFile file = fProject.getFile(filename); + assertNotNull(file); + assertTrue(file.exists()); + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file, CUIPlugin.EDITOR_ID); + assertNotNull(editor); + CEditor ceditor = (CEditor) editor.getAdapter(CEditor.class); + assertNotNull(ceditor); + + // NOTE: This offset relies on the above comment being exactly as expected. If it is edited, + // then this offset should be adjusted to match. It needs to put the cursor in the + // declaration for signal1. + ceditor.setSelection(new SourceRange(86, 0), true); + ISelection sel = ceditor.getSelectionProvider().getSelection(); + assertNotNull(sel); + assertTrue(sel instanceof ITextSelection); + + // Now a query is created and executed. + CSearchTextSelectionQuery query = new CSearchTextSelectionQuery(null, ceditor.getInputCElement(), (ITextSelection) sel, IIndex.FIND_REFERENCES); + + // The query sometimes fails (with Status.CANCEL_STATUS) if the TU is not open. I + // haven't found a way to be notified when the TU gets "opened" -- the test case just + // looks that case and then try again. + IStatus status = null; + long end_ms = System.currentTimeMillis() + 1000; + do { + status = query.run(npm()); + if (status == Status.CANCEL_STATUS) { + Thread.sleep(100); + } + } while(!status.isOK() && System.currentTimeMillis() < end_ms); + assertTrue("query failed: " + status.getMessage(), status.isOK()); + + // The query should have found two references, one for the function call and another + // for the SIGNAL expansion. + ISearchResult result = query.getSearchResult(); + assertNotNull(result); + assertTrue(result instanceof CSearchResult); + + CSearchResult searchResult = (CSearchResult) result; + assertEquals(2, searchResult.getMatchCount()); + } }