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()); + } }