diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java index 843ce659c20..3968b84c1fb 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java @@ -165,7 +165,10 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor; public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser { private static final int DEFAULT_PARM_LIST_SIZE = 4; private static final int DEFAULT_CATCH_HANDLER_LIST_SIZE= 4; - private static enum DtorStrategy {PREFER_FUNCTION, PREFER_NESTED} + + // This is a parameter to the protected function {@link #declarator(DtorStrategy, DeclarationOptions)} + // so it needs to be protected too. + protected static enum DtorStrategy {PREFER_FUNCTION, PREFER_NESTED} private final boolean allowCPPRestrict; private final boolean supportExtendedTemplateSyntax; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/QtParser.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/QtParser.java new file mode 100644 index 00000000000..626e82056ae --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/QtParser.java @@ -0,0 +1,116 @@ +/* + * 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.parser; + +import org.eclipse.cdt.core.dom.ast.IASTDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclarator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTParameterDeclaration; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId; +import org.eclipse.cdt.core.dom.parser.cpp.GPPParserExtensionConfiguration; +import org.eclipse.cdt.core.parser.EndOfFileException; +import org.eclipse.cdt.core.parser.IToken; +import org.eclipse.cdt.core.parser.NullLogService; +import org.eclipse.cdt.core.parser.ParserMode; +import org.eclipse.cdt.internal.core.dom.parser.BacktrackException; +import org.eclipse.cdt.internal.core.dom.parser.DeclarationOptions; +import org.eclipse.cdt.internal.core.dom.parser.cpp.GNUCPPSourceParser; + +/** + * A parser that use a special StringScanner to extract small sections of C++ syntax that + * are used in Qt macro expansions. + * + * @see StringScanner + */ +@SuppressWarnings("restriction") +public class QtParser extends GNUCPPSourceParser { + + private QtParser(String str) { + super(new StringScanner(str), ParserMode.QUICK_PARSE, new NullLogService(), GPPParserExtensionConfiguration.getInstance()); + } + + /** + * The argument String is the expansion parameter for SIGNAL and SLOT macros. The text + * is parsed and the function declarator is returned if possible. Returns null if the + * string is not a valid function declarator reference. + */ + public static ICPPASTFunctionDeclarator parseQtMethodReference(String str) { + // Reject strings that have embedded line terminators. This is needed to properly check that + // one that is about to be added. + if (str == null + || str.contains(";")) + return null; + + QtParser parser = new QtParser(str + ';'); + try { + IASTDeclarator declarator + = parser.declarator(GNUCPPSourceParser.DtorStrategy.PREFER_FUNCTION, DeclarationOptions.CPP_MEMBER); + if (!(declarator instanceof ICPPASTFunctionDeclarator)) + return null; + + // JI 439374: Make sure the ; was the last token read to prevent errors where extra strings + // appear in the expansion parameter. + if (parser.lastTokenFromScanner == null + || parser.lastTokenFromScanner.getType() != IToken.tSEMI) + return null; + + // JI 439374: Make sure the ; was the last token read to prevent errors where extra strings + // appear in the expansion parameter. + if (parser.lastTokenFromScanner == null + || parser.lastTokenFromScanner.getType() != IToken.tSEMI) + return null; + + // make sure this is a legal declarator for a Qt method reference + ICPPASTFunctionDeclarator function = (ICPPASTFunctionDeclarator) declarator; + + // 1) parameters must not have names + for(ICPPASTParameterDeclaration param : function.getParameters()) { + ICPPASTDeclarator decltor = param.getDeclarator(); + if (decltor == null) + continue; + + IASTName paramName = decltor.getName(); + if (paramName == null) + continue; + + char[] name = paramName.getSimpleID(); + if (name == null + || name.length <= 0) + continue; + + // The Qt normalization code treats a reference with a trailing const as a special case (this + // seems to be a bug in the way they normalize const pointers. We could support this case by + // allowing reference parameters to be named 'const'. However, since this seems to be a bug + // in Qt they will likely fix it at some point, and there doesn't seem to be a case where the + // user would need to reference the Qt method in this way. + + // the parameter has a non-empty name, so reject the declarator + return null; + } + + // All tests have passed, so return this declarator. + return function; + } catch(BacktrackException e) { + return null; + } catch(EndOfFileException e) { + return null; + } + } + + public static ICPPASTTypeId parseTypeId(String str) { + QtParser parser = new QtParser(str); + try { + return parser.typeId(new DeclarationOptions(DeclarationOptions.NO_INITIALIZER)); + } catch(BacktrackException e) { + return null; + } catch(EndOfFileException e) { + return null; + } + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/StringScanner.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/StringScanner.java new file mode 100644 index 00000000000..6112addcecf --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/parser/StringScanner.java @@ -0,0 +1,118 @@ +/* + * 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.parser; + +import java.util.Map; + +import org.eclipse.cdt.core.dom.ast.IMacroBinding; +import org.eclipse.cdt.core.parser.EndOfFileException; +import org.eclipse.cdt.core.parser.IScanner; +import org.eclipse.cdt.core.parser.IToken; +import org.eclipse.cdt.core.parser.IncludeExportPatterns; +import org.eclipse.cdt.core.parser.Keywords; +import org.eclipse.cdt.core.parser.OffsetLimitReachedException; +import org.eclipse.cdt.core.parser.util.CharArrayIntMap; +import org.eclipse.cdt.internal.core.parser.scanner.ILexerLog; +import org.eclipse.cdt.internal.core.parser.scanner.ILocationResolver; +import org.eclipse.cdt.internal.core.parser.scanner.Lexer; +import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions; + +/** + * The standard CDT scanner is CPreprocessor, which uses a Lexer to read from a file. The + * relationships look like: + *
+ * GNUCPPSourceParser - CPreprocessor - Lexer + *

+ * The implementation of CPreprocessor depends on reading from a file. It might be possible + * to configure it to get content from a String instead, but it seems like a complex change. + * This simpler solution replaces the CPreprocessor with a simple scanner. In this context, + * the only part of CPreprocessor that seems to be needed is replacing the token type for + * keywords. In this case the relationships look like: + *
+ * QtParser - StringScanner - Lexer + */ +@SuppressWarnings("restriction") +public class StringScanner implements IScanner { + + private final Lexer lexer; + private final CharArrayIntMap keywords; + + public StringScanner(String str) { + this.lexer = new Lexer(str.toCharArray(), new LexerOptions(), ILexerLog.NULL, null); + keywords = new CharArrayIntMap(40, -1); + Keywords.addKeywordsCpp(keywords); + } + + @Override + public IToken nextToken() throws EndOfFileException { + + IToken token = lexer.nextToken(); + if (token.getType() != IToken.tIDENTIFIER) + return token; + + char[] name= token.getCharImage(); + int tokenType = keywords.get(name); + if (tokenType != keywords.undefined) + token.setType(tokenType); + + return token; + } + + @Override + public Map getMacroDefinitions() { + return null; + } + + @Override + public boolean isOnTopContext() { + return false; + } + + @Override + public void cancel() { + } + + @Override + public ILocationResolver getLocationResolver() { + return null; + } + + @Override + public void setTrackIncludeExport(IncludeExportPatterns patterns) { + } + + @Override + public void setContentAssistMode(int offset) { + } + + @Override + public void setSplitShiftROperator(boolean val) { + } + + @Override + public void setComputeImageLocations(boolean val) { + } + + @Override + public void setProcessInactiveCode(boolean val) { + } + + @Override + public void skipInactiveCode() throws OffsetLimitReachedException { + } + + @Override + public int getCodeBranchNesting() { + return 0; + } + + @Override + @Deprecated + public void setScanComments(boolean val) { + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMethod.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMethod.java new file mode 100644 index 00000000000..c2efe891e5f --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMethod.java @@ -0,0 +1,69 @@ +/* + * 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.qt.core.index; + +import java.util.Collection; + +/** + * Qt provides macros for marking member functions as special. The moc compiler + * recognizes these annotations and generates extra code to implement the special + * behaviour. + * + * This interface is used to represent these methods in the Qt index. It is used + * for member functions that have been marked as signals, slots, and invokables. + */ +public interface IQMethod extends IQElement, IQObject.IMember { + + /** + * The kind of Qt annotation that has been applied to this member function. + * Signals and slots are implicitly invokable, if a single member function + * has been tagged with both signal/slot and invokable, the kind will be + * Signal or Slot. + */ + public static enum Kind { + Unspecified, + Invokable, + Signal, + Slot; + } + + /** + * The kind of Qt annotation that has been applied to this member function. Signals and + * slots are implicitly invokable, if a single member function has been tagged with both + * signal and invokable, the kind will be Signal (and likewise for Slot). + */ + public Kind getKind(); + + /** + * Returns the function name of the method. + */ + public String getName(); + + /** + * Returns the normalized C++ function signatures of the receiver method. There is + * more than one signature only when at least one parameter has a default value. + * E.g., for + * #signal1 in: + *

+	 * class T : public QObject
+	 * {
+	 * Q_OBJECT
+	 * Q_SIGNAL void signal1( int = 5 );
+	 * };
+	 * 
+ * This would return "{ signal1(int), signal1() }". + */ + public Collection getSignatures(); + + /** + * Return the revision if this method was tagged with the Q_REVISION macro and null + * otherwise. The return type is Long in order to accommodate unsigned C++ 32-bit + * values. + */ + public Long getRevision(); +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQObject.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQObject.java index 17670e26a5e..383ca73112b 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQObject.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQObject.java @@ -86,6 +86,21 @@ public interface IQObject extends IQElement { */ public List getBases(); + /** + * Returns the methods that have been tagged as Qt slots. Does not return null. + */ + public IMembers getSlots(); + + /** + * Returns the methods that have been tagged as Qt signals. Does not return null. + */ + public IMembers getSignals(); + + /** + * Returns the methods that have been tagged with Q_INVOKABLE. Does not return null. + */ + public IMembers getInvokables(); + /** * Returns the expansions of the Q_PROPERTY macro. Does not return null. */ diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtMethodUtil.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtMethodUtil.java new file mode 100644 index 00000000000..c25a7e511c1 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtMethodUtil.java @@ -0,0 +1,345 @@ +/* + * 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.qt.internal.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.bind.DatatypeConverter; + +import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; +import org.eclipse.cdt.core.dom.ast.IASTDeclarator; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTPointer; +import org.eclipse.cdt.core.dom.ast.IASTPointerOperator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclarator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTParameterDeclaration; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTReferenceOperator; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId; +import org.eclipse.cdt.internal.core.dom.parser.ASTAmbiguousNode; +import org.eclipse.cdt.internal.qt.core.parser.QtParser; + +/** + * A collection of utility functions for dealing with Qt methods. A Qt method is a normal + * C++ method that has been annotated with empty macro expansions. + */ +@SuppressWarnings("restriction") +public class QtMethodUtil { + + /** + * The Qt implementation uses specific rules for generating a signature that is used + * to map between invokable function declarations and their use. This function has + * be implemented by comparing the output of moc to the various test cases in the + * qt test suite. + */ + public static String getQtNormalizedMethodSignature(String signature) { + + ICPPASTFunctionDeclarator function = QtParser.parseQtMethodReference(signature); + if (function == null) + return null; + + // NOTE: This implementation (both here and in methods that are invoked) used call #getRawSignature + // to get the original tokens. This has been changed to use #toString instead. They seem to + // provide the same value, so this should be OK. The problem with #getRawSignature is that it + // looks for the characters in the file (using offset and length). There isn't a file backing + // the StringScanner, so the result is the empty String. If we find cases where #toString + // returns the wrong value, then this can be changed back to #getRawSignature. Implement the + // AST and LocationResolver to work with ASTNode#getRawSignatureChars: + // protected char[] getRawSignatureChars() { + // final IASTFileLocation floc= getFileLocation(); + // final IASTTranslationUnit ast = getTranslationUnit(); + // if (floc != null && ast != null) { + // ILocationResolver lr= (ILocationResolver) ast.getAdapter(ILocationResolver.class); + // if (lr != null) { + // return lr.getUnpreprocessedSignature(getFileLocation()); + // } + // } + + StringBuilder result = new StringBuilder(); + + // raw sig tries to find the file + String fnName = function.getName().getLastName().toString(); + result.append(stripWS(fnName)); + result.append('('); + + boolean first = true; + for(ICPPASTParameterDeclaration param : function.getParameters()) { + if (first) + first = false; + else + result.append(','); + + IASTDeclSpecifier spec = param.getDeclSpecifier(); + ICPPASTDeclarator declarator = param.getDeclarator(); + + // The parameters are encoded so that we can rely on , being used to separate + // parameters. All other commas (e.g., to separate template arguments within + // the parameter type) will be encoded. + StringBuilder paramSig = new StringBuilder(); + append(paramSig, spec, declarator, true); + + result.append(stripWS(paramSig.toString())); + } + + result.append(')'); + + // Whitespace around operators is not needed, remove it to normalize the signature. + return result.toString(); + } + + public static Collection getDecodedQtMethodSignatures(String qtEncSignatures) { + if (qtEncSignatures == null) + return null; + + StringBuilder signature = new StringBuilder(); + int i = qtEncSignatures.indexOf('('); + String name = qtEncSignatures.substring(0, i); + + signature.append(name); + signature.append('('); + + boolean first = true; + List signatures = new ArrayList(); + qtEncSignatures = qtEncSignatures.substring(i + 1); + Pattern p = Pattern.compile("^([a-zA-Z0-9+/=]*)(@?).*$"); + while(!qtEncSignatures.isEmpty()) { + Matcher m = p.matcher(qtEncSignatures); + if (!m.matches()) + break; + + int next = m.end(2) + 1; + qtEncSignatures = qtEncSignatures.substring(next); + + String param = new String(DatatypeConverter.parseBase64Binary(m.group(1))); + + // If this parameter has a default value, then add a signature for the method up + // to this point. + if (!m.group(2).isEmpty()) + signatures.add(signature.toString() + ')'); + + if (first) + first = false; + else + signature.append(','); + signature.append(param); + } + + signature.append(')'); + signatures.add(signature.toString()); + return signatures; + } + + /** + * The Qt implementation has specific rules for generating a signature that is used + * to map between invokable function declarations and their use. This function has + * been implemented by comparing the output of moc to the various test cases in the + * Qt test suite. + */ + public static String getEncodedQtMethodSignatures(ICPPASTFunctionDeclarator function) { + StringBuilder result = new StringBuilder(); + + String fnName = function.getName().getLastName().toString(); + result.append(stripWS(fnName)); + result.append('('); + + boolean first = true; + for(ICPPASTParameterDeclaration param : function.getParameters()) { + if (first) + first = false; + else + result.append(','); + + IASTDeclSpecifier spec = param.getDeclSpecifier(); + ICPPASTDeclarator declarator = param.getDeclarator(); + + // The parameters are encoded so that we can rely on , being used to separate + // parameters. All other commas (e.g., to separate template arguments within + // the parameter type) will be encoded. + StringBuilder paramSig = new StringBuilder(); + append(paramSig, spec, declarator, true); + + String paramStr = stripWS(paramSig.toString()); + result.append(DatatypeConverter.printBase64Binary(paramStr.getBytes())); + + // A special character is used as a suffix on parameters that have a default value. + // A previous version of this implementation used '=' within the Base64 encoded + // payload. Now that the initializer flag is outside of the payload, '=' is a bad + // choice because it is also a valid Base64 encoded character. + // Like all the other parts of this encoder, the @ must match the value that is used + // in the decoder. + if (declarator.getInitializer() != null) + result.append('@'); + } + + result.append(')'); + + // Whitespace around operators is not needed, remove it to normalize the signature. + return result.toString(); + } + + private static String stripWS(String str) { + return str + .trim() + .replaceAll("\\s+", " ") + .replaceAll(" ([\\*&,()<>]+)", "$1") + .replaceAll("([\\*&,()<>]+) ", "$1"); + } + + private static String asString(IASTPointerOperator ptr) { + if (ptr instanceof ICPPASTReferenceOperator) + return "&"; + if (ptr instanceof IASTPointer) { + StringBuilder str = new StringBuilder(); + IASTPointer astPtr = (IASTPointer) ptr; + str.append('*'); + if (astPtr.isConst()) + str.append(" const"); + if (astPtr.isVolatile()) + str.append(" volatile"); + return str.toString(); + } + + return ptr.toString(); + } + + private static void append(StringBuilder result, IASTDeclSpecifier spec, IASTDeclarator declarator, boolean pruneConst) { + IASTPointerOperator[] ptrs = declarator.getPointerOperators(); + if (ptrs == null) + ptrs = new IASTPointerOperator[0]; + + if (!(spec instanceof ICPPASTDeclSpecifier)) { + result.append(spec.toString()); + return; + } + + ICPPASTDeclSpecifier cppSpec = (ICPPASTDeclSpecifier) spec; + + // Qt considers the type const if it is marked as const, or if it is a reference + // and the previous pointer is const. I.e., we need this: + // const T& -> T + // const T* const & -> T* + boolean isConst = cppSpec.isConst(); + boolean stripLastPtrConst + = pruneConst + && !isConst + && (ptrs.length >= 2 + && ptrs[ptrs.length - 1] instanceof ICPPASTReferenceOperator + && ptrs[ptrs.length - 2] instanceof IASTPointer + && ((IASTPointer) ptrs[ptrs.length - 2]).isConst()); + + if (isConst || stripLastPtrConst) { + if (!pruneConst) + result.append("const "); + else { + // Qt signature generation converts const value and const reference types + // into simple value types. E.g., + // const T => T + // const T & => T + // From observation, they also convert const pointer to const to const + // pointers although I think that is a bug, because simple pointer to + // const are not converted to simple pointers. E.g., + // const T * => const T * + // const T * const => T * const + if (ptrs.length > 0) { + IASTPointerOperator lastPtr = ptrs[ptrs.length - 1]; + if (lastPtr instanceof ICPPASTReferenceOperator) + ptrs = Arrays.copyOf(ptrs, ptrs.length - 1); + else if (!(lastPtr instanceof IASTPointer) + || !((IASTPointer) lastPtr).isConst()) + result.append("const "); + } + } + } + + // Qt does no special handling for volatile. This is likely an oversight. + if (cppSpec.isVolatile()) + result.append("volatile "); + + IASTNode[] children = cppSpec.getChildren(); + if (children == null || children.length <= 0) { + // We use the raw signature to get the text that was used to reference the + // type (without following typedefs, etc.), and then strip out all const + // which has already been handled. + String raw = cppSpec.toString(); + raw = raw.replaceAll("const\\s", ""); + raw = raw.replaceAll("\\sconst", ""); + result.append(raw); + } else { + for(IASTNode child : children) { + result.append( ' ' ); + if (child instanceof ICPPASTTemplateId) { + ICPPASTTemplateId templId = (ICPPASTTemplateId) child; + result.append(templId.getTemplateName()); + result.append('<'); + for(IASTNode templArg : templId.getTemplateArguments()) { + append(result, templArg); + } + result.append('>'); + } else + result.append(child.toString()); + } + } + + // exclude param name, use '=' to indicate an initial value + for(int i = 0; i < ptrs.length; ++i) { + if (!stripLastPtrConst + || i < ptrs.length - 1) + result.append(asString(ptrs[i])); + else + result.append(asString(ptrs[i]).replaceAll("const", "")); + } + } + + private static void append(StringBuilder result, IASTNode node) { + + // JI476551: When the code is parsed without full context, e.g., when parsing a Qt method ref, an + // ambiguous node could be created. Since we only need the original text, we can use + // any of the nodes that triggered the ambiguity. Arbitrarily choose the first one. + if (node instanceof ASTAmbiguousNode) { + IASTNode[] nodes = ((ASTAmbiguousNode) node).getNodes(); + if (nodes != null + && nodes.length > 0) { + append(result, nodes[0]); + return; + } + } + + if (node instanceof ICPPASTTypeId) { + ICPPASTTypeId typeId = (ICPPASTTypeId) node; + IASTDeclSpecifier spec = typeId.getDeclSpecifier(); + IASTDeclarator declarator = typeId.getAbstractDeclarator(); + append(result, spec, declarator, false); + return; + } + + if (!(node instanceof ICPPASTTemplateId)) { + result.append(node.toString()); + return; + } + + ICPPASTTemplateId templId = (ICPPASTTemplateId) node; + result.append(templId.getTemplateName()); + result.append('<'); + boolean first = true; + for (IASTNode child : templId.getTemplateArguments()) { + if (first) + first = false; + else + result.append(", "); + append(result, child); + } + result.append('>'); + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/AbstractQField.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/AbstractQField.java index 9d11ebb2621..90c97e0bd12 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/AbstractQField.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/AbstractQField.java @@ -15,14 +15,6 @@ public abstract class AbstractQField implements IQObject.IMember { private final IQObject owner; protected String name; - /** - * Scan the given field and extracts the strings defining the attributes of the - * field. Returns false if the expansion parameter, does not represent a Q_PROPERTY, - * does not have related information, or if the information does not match the - * expected format. - */ - protected abstract boolean scanDefn(String expansionParam); - protected AbstractQField(IQObject owner) { this.owner = owner; } @@ -37,8 +29,8 @@ public abstract class AbstractQField implements IQObject.IMember { if (!AbstractQField.class.isAssignableFrom(member.getClass())) return false; - // I haven't been able to find Qt documentation describing how Q_PROPERY is overridden, - // but the docs suggest it is just by name. + // I haven't been able to find Qt documentation describing how things like + // Q_PROPERY are overridden, but the docs suggest it is just by name. AbstractQField other = (AbstractQField) member; return name == null ? other.name == null : name.equals(other.name); diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QMethod.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QMethod.java new file mode 100644 index 00000000000..8c5bc7574aa --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QMethod.java @@ -0,0 +1,99 @@ +/* + * 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.qt.internal.core.index; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.qt.core.index.IQMethod; +import org.eclipse.cdt.qt.core.index.IQObject; +import org.eclipse.cdt.qt.core.index.IQObject.IMember; +import org.eclipse.cdt.qt.internal.core.QtMethodUtil; +import org.eclipse.cdt.qt.internal.core.pdom.QtPDOMQMethod; +import org.eclipse.core.runtime.CoreException; + +public class QMethod implements IQMethod { + + private final IQObject owner; + private final String name; + private final IQMethod.Kind kind; + private final Collection signatures; + private final Long revision; + + public QMethod(IQObject owner, QtPDOMQMethod pdom) throws CoreException { + this.owner = owner; + this.name = pdom.getName(); + this.kind = pdom.getKind(); + this.signatures = QtMethodUtil.getDecodedQtMethodSignatures(pdom.getQtEncodedSignatures()); + this.revision = pdom.getRevision(); + } + + @Override + public boolean isOverride(IMember member) { + if (!IQMethod.class.isAssignableFrom(member.getClass())) + return false; + + // Methods override when they have the same name and type. + + IQMethod other = (IQMethod) member; + + if (name == null) { + if (other.getName() != null) + return false; + } else if (!name.equals(other.getName())) + return false; + + IBinding otherBinding = other.getBinding(); + if (otherBinding == null) + return getBinding() == null; + + return false ;// TODO +// if (!ICPPMethod.class.isAssignableFrom(otherBinding.getClass())) +// return false; +// +// IType thisType = method.getType(); +// IType otherType = ((ICPPMethod) otherBinding).getType(); +// return thisType == null ? otherType == null : thisType.isSameType(otherType); + } + + @Override + public IBinding getBinding() { + return null; // TODO method; + } + + @Override + public IQObject getOwner() { + return owner; + } + + @Override + public Kind getKind() { + return kind; + } + + @Override + public String getName() { + return name; + } + + @Override + public Collection getSignatures() { + return signatures == null ? Collections.emptyList() : signatures; + } + + @Override + public Long getRevision() { + return revision; + } + + @Override + public String toString() { + return kind.toString() + ' ' + signatures; + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QObject.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QObject.java index 9a050d5b638..0f5b83d3faf 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QObject.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QObject.java @@ -14,10 +14,12 @@ import java.util.Map; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.qt.core.index.IQEnum; +import org.eclipse.cdt.qt.core.index.IQMethod; import org.eclipse.cdt.qt.core.index.IQObject; import org.eclipse.cdt.qt.core.index.IQProperty; import org.eclipse.cdt.qt.internal.core.pdom.QtPDOMProperty; import org.eclipse.cdt.qt.internal.core.pdom.QtPDOMQEnum; +import org.eclipse.cdt.qt.internal.core.pdom.QtPDOMQMethod; import org.eclipse.cdt.qt.internal.core.pdom.QtPDOMQObject; import org.eclipse.core.runtime.CoreException; @@ -26,6 +28,9 @@ public class QObject implements IQObject { private final String name; private final QtPDOMQObject pdomQObject; private final List bases; + private final IQObject.IMembers slots; + private final IQObject.IMembers signals; + private final IQObject.IMembers invokables; private final IQObject.IMembers properties; private final List enums; private final Map classInfos; @@ -34,6 +39,9 @@ public class QObject implements IQObject { this.name = pdomQObject.getName(); this.pdomQObject = pdomQObject; + List baseSlots = new ArrayList(); + List baseSignals = new ArrayList(); + List baseInvokables = new ArrayList(); List baseProps = new ArrayList(); this.bases = new ArrayList(); @@ -45,13 +53,35 @@ public class QObject implements IQObject { this.classInfos = pdomQObject.getClassInfos(); + List slots = new ArrayList(); + List signals = new ArrayList(); + List invokables = new ArrayList(); + for(QtPDOMQMethod pdom : pdomQObject.getChildren(QtPDOMQMethod.class)) + switch(pdom.getKind()) { + case Slot: + slots.add(new QMethod(this, pdom)); + break; + case Signal: + signals.add(new QMethod(this, pdom)); + break; + case Invokable: + invokables.add(new QMethod(this, pdom)); + break; + case Unspecified: + break; + } + + this.slots = QObjectMembers.create(slots, baseSlots); + this.signals = QObjectMembers.create(signals, baseSignals); + this.invokables = QObjectMembers.create(invokables, baseInvokables); + this.enums = new ArrayList(); - for(QtPDOMQEnum pdom : pdomQObject.getFields(QtPDOMQEnum.class)) + for(QtPDOMQEnum pdom : pdomQObject.getChildren(QtPDOMQEnum.class)) this.enums.add(new QEnum(pdom.getName(), pdom.isFlag(), pdom.getEnumerators())); List props = new ArrayList(); - for(QtPDOMProperty pdom : pdomQObject.getFields(QtPDOMProperty.class)) { - QProperty qProp = new QProperty(this, pdom.getTypeStr(), pdom.getName()); + for(QtPDOMProperty pdom : pdomQObject.getChildren(QtPDOMProperty.class)) { + QProperty qProp = new QProperty(this, pdom.getType(), pdom.getName()); for(QtPDOMProperty.Attribute attr : pdom.getAttributes()) qProp.setAttribute(attr.attr, attr.value); props.add(qProp); @@ -74,6 +104,21 @@ public class QObject implements IQObject { return bases; } + @Override + public IMembers getSlots() { + return slots; + } + + @Override + public IMembers getSignals() { + return signals; + } + + @Override + public IMembers getInvokables() { + return invokables; + } + @Override public IQObject.IMembers getProperties() { return properties; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QProperty.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QProperty.java index 8d0bf81f606..f8e2f957718 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QProperty.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/index/QProperty.java @@ -7,9 +7,6 @@ */ package org.eclipse.cdt.qt.internal.core.index; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.eclipse.cdt.qt.core.index.IQObject; import org.eclipse.cdt.qt.core.index.IQProperty; @@ -24,110 +21,6 @@ public class QProperty extends AbstractQField implements IQProperty { this.name = name; } - /** - * A regular expression for scanning the full Q_PROPERTY expansion and extracting the - * expansion parameter. It provides the following capture groups: - * 1 - the type - * 2 - the name - * 3 - the trimmed remainder of the expansion parameter (starting with READ) - * - * This REGEX handles cases like: - * Q_PROPERTY(T* t READ ... ) - * Q_PROPERTY(T * t READ ... ) - * Q_PROPERTY(T *t READ ... ) - * This REGEX assumes that READ will directly follow the property name. This is implied, - * although not explicitly stated in the Qt documentation. - * - * It also allows the option of having no other attribute (just type and name). The Qt - * documentation forbids this, but it is used in QtSensors/ - */ - private static final Pattern EXPANSION_REGEX = Pattern.compile("^(.+?)\\s*([a-zA-Z_][\\w]*+)(?:(?:\\s+(READ\\s+.*))|\\s*)$"); - - /** - * A regular expression for scanning Q_PROPERTY attributes. The regular expression is built - * from the values defined in IQProperty#Attribute. It looks like: - *
-	 * (:?READ)|(?:WRITE)|(:?RESET)|...
-	 * 
- * This regular expression is used to recognize valid attributes while scanning the - * Q_PROPERTY macro expansion. - * - * @see QProperty#scanAttributes(String) - */ - private static final Pattern ATTRIBUTE_REGEX; - static { - StringBuilder regexBuilder = new StringBuilder(); - for(IQProperty.Attribute attr : IQProperty.Attribute.values()) { - if (attr.ordinal() > 0) - regexBuilder.append('|'); - regexBuilder.append("(:?"); - regexBuilder.append(attr.identifier); - regexBuilder.append(")"); - } - ATTRIBUTE_REGEX = Pattern.compile(regexBuilder.toString()); - } - - /** - * Scans the given field and extracts the strings defining the attributes of the - * Q_PROPERTY. Returns false if the field is does not represent a Q_PROPERTY, does - * not have attribute-related information, or if the information does not match the - * expected format. - * @param field - * @return - */ - @Override - protected boolean scanDefn(String expansionParam) { - Matcher m = EXPANSION_REGEX.matcher(expansionParam); - if (!m.matches()) - return false; - - this.type = m.group(1); - this.name = m.group(2); - return scanAttributes(m.group(3)); - } - - /** - * Scans the given string to extract values for all recognized attributes. A regular expression - * is used to find the attributes, substrings between attributes are assigned as values. - * Attributes that don't expect a value (as determined by {@link IQProperty#Attribute#hasValue}), - * as assigned "true". - */ - private boolean scanAttributes(String attributes) { - if (attributes == null) - return true; - - int lastEnd = 0; - IQProperty.Attribute lastAttr = null; - for(Matcher attributeMatcher = ATTRIBUTE_REGEX.matcher(attributes); attributeMatcher.find(); lastEnd = attributeMatcher.end()) { - // set the value of attribute found in the previous iteration to the substring between - // the end of that attribute and the start of this one - if (lastAttr != null) { - String value = attributes.substring(lastEnd, attributeMatcher.start()); - values[lastAttr.ordinal()] = value.trim(); - } - - // the regex is built from the definition of the enum, so none of the strings that it - // finds will throw an exception - lastAttr = IQProperty.Attribute.valueOf(IQProperty.Attribute.class, attributeMatcher.group(0)); - - // if this attribute doesn't have a value, then put it into the value map immediately - // and make sure it is not used later in this scan - if (!lastAttr.hasValue) { - values[lastAttr.ordinal()] = Boolean.TRUE.toString(); - lastAttr = null; - } - } - - // the value of the last attribute in the expansion is the substring between the end of - // the attribute identifier and the end of the string - if (lastAttr != null) { - String value = attributes.substring(lastEnd); - values[lastAttr.ordinal()] = value.trim(); - } - - return true; - } - public void setAttribute(IQProperty.Attribute attr, String value) { values[attr.ordinal()] = ( value == null ? "" : value ); } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectFieldName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectMemberName.java similarity index 91% rename from qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectFieldName.java rename to qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectMemberName.java index a9bf082364d..94eff5904e7 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectFieldName.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/AbstractQObjectMemberName.java @@ -19,14 +19,14 @@ import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding; import org.eclipse.core.runtime.CoreException; @SuppressWarnings("restriction") -public abstract class AbstractQObjectFieldName extends ASTDelegatedName { +public abstract class AbstractQObjectMemberName extends ASTDelegatedName { private final QObjectName owner; private final String name; - private final QtASTImageLocation location; + private final IASTImageLocation location; private ASTNodeProperty propertyInParent; - protected AbstractQObjectFieldName(QObjectName owner, IASTName ast, String name, QtASTImageLocation location) { + protected AbstractQObjectMemberName(QObjectName owner, IASTName ast, String name, IASTImageLocation location) { super(ast); this.owner = owner; this.name = name; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QMethodName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QMethodName.java new file mode 100644 index 00000000000..59c08673d6b --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QMethodName.java @@ -0,0 +1,31 @@ +/* + * 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.qt.internal.core.pdom; + +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.qt.core.index.IQMethod; +import org.eclipse.core.runtime.CoreException; + +public class QMethodName extends AbstractQObjectMemberName implements IQtASTName { + + private final IQMethod.Kind kind; + private final String qtEncSignatures; + private final Long revision; + + public QMethodName(QObjectName qobjName, IASTName cppName, IQMethod.Kind kind, String qtEncSignatures, Long revision) { + super(qobjName, cppName, cppName.getLastName().toString(), cppName.getImageLocation()); + this.kind = kind; + this.qtEncSignatures = qtEncSignatures; + this.revision = revision; + } + + @Override + public QtPDOMBinding createPDOMBinding(QtPDOMLinkage linkage) throws CoreException { + return new QtPDOMQMethod(linkage, getOwner(linkage), this, delegate, kind, qtEncSignatures, revision); + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTClass.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTClass.java new file mode 100644 index 00000000000..7f873cf29c8 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTClass.java @@ -0,0 +1,380 @@ +/* + * 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.qt.internal.core.pdom; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cdt.core.dom.ast.IASTDeclaration; +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.IASTNodeLocation; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel; +import org.eclipse.cdt.qt.core.QtKeywords; +import org.eclipse.cdt.qt.core.index.IQMethod; + +/** + * The AST for a QObject is separated into regions based on macro expansions. These + * regions determine the Qt kind for methods that are declared within them. + *

+ * This utility class makes one pass over the C++ class specification to identify + * all such regions. It also provides an iterator that can be used while examining + * the class spec's members. + */ +public class QtASTClass { + + private final Iterator regions; + private final Iterator tags; + private final Iterator revisions; + private Region region; + private Tag tag; + private Revision revision; + + /** + * Must only be called with increasing offset. Internal pointers may be advanced on + * each call. + */ + public IQMethod.Kind getKindFor(int offset) { + + // There are 3 steps: + // 1) The tags counter must always be advanced. Tags only apply to the next declaration + // and therefore the internal counter must always be advanced. Multiple tags are + // collapsed to find the highest precedence value. + // 2) The region counter is advanced to find a region that either contains the offset + // or is the first region after the offset. Regions override tags, so we use the + // region kind if one is found. + // 3) The final result is based on tags (if they were present). + // + // This precedence is based on experimentation with the moc (ver 63). It + // ignores macros tagging a single method when that method is declared within + // a signal/slot region. E.g., the following example has two signals and one slot: + // + // class Q : public QObject + // { + // Q_OBJECT + // signals: void signal1(); + // Q_SLOT void signal2(); /* Tagged with Q_SLOT, but the declaration is within the + // * signals region, so the moc considers it a signal. */ + // public: + // Q_SLOT void slot1(); + // }; + + + // Consume all tags since the last declaration to find the highest precedence tag. + IQMethod.Kind kind = IQMethod.Kind.Unspecified; + while(tag != null && tag.offset < offset) { + kind = getHigherPrecedence(kind, tag.kind); + tag = tags.hasNext() ? tags.next() : null; + } + + // Advance regions to find one that does not end before this offset. + while(region != null && region.end < offset) + region = regions.hasNext() ? regions.next() : null; + + // If the offset is within this region, then use its kind. + if (region != null && region.contains(offset)) + kind = region.kind; + + return kind; + } + + /** + * Must only be called with increasing offset. Internal pointers may be advanced on + * each call. + */ + public Long getRevisionFor(int offset) { + + // Consume all revisions since the last declaration to find one (if any) that applies + // to this declaration. + Long rev = null; + while(revision != null && revision.offset < offset) { + rev = revision.revision; + revision = revisions.hasNext() ? revisions.next() : null; + } + + return rev; + } + + private static IQMethod.Kind getHigherPrecedence(IQMethod.Kind kind1, IQMethod.Kind kind2) { + switch(kind1) { + case Unspecified: + return kind2; + case Invokable: + switch(kind2) { + case Slot: + case Signal: + return kind2; + default: + return kind1; + } + case Signal: + if (kind2 == IQMethod.Kind.Slot) + return kind2; + return kind2; + case Slot: + return kind1; + } + return IQMethod.Kind.Unspecified; + } + + public static QtASTClass create(ICPPASTCompositeTypeSpecifier spec) { + + // There is more detail in Bug 401696 describing why this needs to look at all + // the node locations. Briefly, the CDT parser does not associate empty macros + // with the function when they are the first thing in the declaration. E.g., + // + // #define X + // void func1() {} + // X void func2() {} + // + // Could also look like: + // void func1() {} X + // void func2() {} + // + // The nodes are processed in three stages which are described in detail below. Only + // the first stage looks at the nodes, the later stages just cleanup results from the + // first walk over all node locations. + + // 1) Examine the locations to find all macro expansions. This finds a beginning and + // highest possible end for the regions. It also locates the offset for single-method + // tags (including resolving precedence). + // This allows single-method tags to overlap regions because regions may be shortened + // by a later step. + ArrayList tags = new ArrayList(); + ArrayList revisions = new ArrayList(); + ArrayList regions = new ArrayList(); + Region currRegion = null; + for(IASTNodeLocation location : spec.getNodeLocations()) { + + Tag tag = Tag.create(location); + if (tag != null) + tags.add(tag); + + Revision revision = Revision.create(location); + if (revision != null) + revisions.add(revision); + + Region region = Region.create(location); + if (region != null) { + if (currRegion != null) + currRegion.end = region.begin; + + currRegion = region; + regions.add(region); + } + } + + // 2) Make the regions smaller where visibility labels are introduced. + if (!regions.isEmpty()) { + Iterator iterator = regions.iterator(); + Region region = iterator.next(); + for (IASTDeclaration decl : spec.getMembers()) { + + // Ignore everything other than visibility labels. + if (!(decl instanceof ICPPASTVisibilityLabel)) + continue; + + int offset = decl.getFileLocation().getNodeOffset(); + + // Otherwise terminate all regions that start before this label and advance + // to the first one that follows. + while(region != null && region.begin < offset) { + region.end = offset; + region = iterator.hasNext() ? iterator.next() : null; + } + + // Stop searching for visibility labels after the last region has been terminated. + if (region == null) + break; + } + } + + // 3) Eliminate tags that are within regions. + if (!tags.isEmpty()) { + Iterator iterator = tags.iterator(); + Tag tag = iterator.next(); + for(Region region : regions) { + + // Keep all tags that are before the start of this region. + while(tag != null && tag.offset < region.begin) + tag = iterator.hasNext() ? iterator.next() : null; + + // Delete all tags that are within this region. + while(tag != null && region.contains(tag.offset)) { + iterator.remove(); + tag = iterator.hasNext() ? iterator.next() : null; + } + + // Stop searching when there are no more tags to be examined. + if (tag == null) + break; + } + } + + return new QtASTClass(regions, tags, revisions); + } + + private QtASTClass(List regions, List tags, List revisions) { + this.regions = regions.iterator(); + this.tags = tags.iterator(); + this.revisions = revisions.iterator(); + + this.region = this.regions.hasNext() ? this.regions.next() : null; + this.tag = this.tags.hasNext() ? this.tags.next() : null; + this.revision = this.revisions.hasNext() ? this.revisions.next() : null; + } + + private static class Region { + public final int begin; + public int end = Integer.MAX_VALUE; + public final IQMethod.Kind kind; + + public Region(int begin, IQMethod.Kind kind) { + this.begin = begin; + this.kind = kind; + } + + public boolean contains(int offset) { + return offset >= begin + && offset < end; + } + + /** + * Return a region for the given location or null if the location does not + * introduce a region. + */ + public static Region create(IASTNodeLocation location) { + if (!(location instanceof IASTMacroExpansionLocation)) + return null; + + IASTMacroExpansionLocation macroLocation = (IASTMacroExpansionLocation) location; + IASTFileLocation fileLocation = macroLocation.asFileLocation(); + if (fileLocation == null) + return null; + + int offset = fileLocation.getNodeOffset(); + IASTPreprocessorMacroExpansion expansion = macroLocation.getExpansion(); + String macroName = getMacroName(expansion); + if (QtKeywords.Q_SLOTS.equals(macroName) + || QtKeywords.SLOTS.equals(macroName)) + return new Region(offset, IQMethod.Kind.Slot); + if (QtKeywords.Q_SIGNALS.equals(macroName) + || QtKeywords.SIGNALS.equals(macroName)) + return new Region(offset, IQMethod.Kind.Signal); + return null; + } + } + + private static class Tag { + public final int offset; + public IQMethod.Kind kind; + + private Tag(int begin, IQMethod.Kind kind) { + this.offset = begin; + this.kind = kind; + } + + /** + * Return a tag for the given location or null if the location does not + * introduce a tag. + */ + public static Tag create(IASTNodeLocation location) { + if (!(location instanceof IASTMacroExpansionLocation)) + return null; + + IASTMacroExpansionLocation macroLocation = (IASTMacroExpansionLocation) location; + IASTFileLocation fileLocation = macroLocation.asFileLocation(); + if (fileLocation == null) + return null; + + int offset = fileLocation.getNodeOffset(); + IASTPreprocessorMacroExpansion expansion = macroLocation.getExpansion(); + String macroName = getMacroName(expansion); + if (QtKeywords.Q_SLOT.equals(macroName)) + return new Tag(offset, IQMethod.Kind.Slot); + if (QtKeywords.Q_SIGNAL.equals(macroName)) + return new Tag(offset, IQMethod.Kind.Signal); + if (QtKeywords.Q_INVOKABLE.equals(macroName)) + return new Tag(offset, IQMethod.Kind.Invokable); + return null; + } + } + + private static class Revision { + private final int offset; + private final Long revision; + + // This regular expression matches Q_REVISION macro expansions. It allows C++ integer + // literals as the expansion parameter. The integer literal is provided in capture + // group 1. Hexadecimal and octal prefixes are included in the capture group. Unsigned + // and long suffixes are allowed but are excluded from the capture group. The matcher's + // input string should be trimmed and have all newlines replaced. + private static final Pattern QREVISION_REGEX = Pattern.compile("^Q_REVISION\\s*\\(\\s*((?:0x)?[\\da-fA-F]+)[ulUL]*\\s*\\)$"); + + public Revision(int offset, Long revision) { + this.offset = offset; + this.revision = revision; + } + + /** + * Return a tag for the given location or null if the location does not + * introduce a tag. + */ + public static Revision create(IASTNodeLocation location) { + if (!(location instanceof IASTMacroExpansionLocation)) + return null; + + IASTMacroExpansionLocation macroLocation = (IASTMacroExpansionLocation) location; + IASTFileLocation fileLocation = macroLocation.asFileLocation(); + if (fileLocation == null) + return null; + + int offset = fileLocation.getNodeOffset(); + IASTPreprocessorMacroExpansion expansion = macroLocation.getExpansion(); + String macroName = getMacroName(expansion); + if (!QtKeywords.Q_REVISION.equals(macroName)) + return null; + + String raw = expansion.getRawSignature(); + if (raw == null) + return null; + + // Trim leading and trailing whitespace and remove all newlines. + Matcher m = QREVISION_REGEX.matcher(raw.trim().replaceAll("\\s+", "")); + if (m.matches()) { + try { + return new Revision(offset, Long.parseLong(m.group(1))); + } catch(NumberFormatException e) { + // The number will be parsed incorrectly when the C++ client code does not + // contain a valid integer. We can't do anything about that, so the exception + // is ignored. A codan checker could notify the user of this problem. + } + } + + return null; + } + } + + /** + * Find and return the simple name of the macro that is being expanded or null if the name + * cannot be found. + */ + private static String getMacroName(IASTPreprocessorMacroExpansion expansion) { + if (expansion == null) + return null; + + IASTName name = expansion.getMacroReference(); + return name == null ? null : name.toString(); + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTVisitor.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTVisitor.java index 6b5256e966b..32c2b12234e 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTVisitor.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtASTVisitor.java @@ -17,22 +17,28 @@ import java.util.regex.Pattern; import org.eclipse.cdt.core.dom.ast.ASTVisitor; 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.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; +import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IScope; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; 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.qt.core.QtKeywords; +import org.eclipse.cdt.qt.core.index.IQMethod; import org.eclipse.cdt.qt.core.index.IQProperty; +import org.eclipse.cdt.qt.internal.core.QtMethodUtil; import org.eclipse.cdt.qt.internal.core.index.QProperty; @SuppressWarnings("restriction") @@ -140,12 +146,7 @@ public class QtASTVisitor extends ASTVisitor { IBinding[] bindings = CPPSemantics.findBindingsForQualifiedName(spec.getScope(), alias == null ? name : alias); for(IBinding binding : bindings) { // Create a reference from this Qt name to the target enum's definition. - IASTName cppName = null; - if (binding instanceof ICPPInternalBinding) { - IASTNode node = ((ICPPInternalBinding) binding).getDefinition(); - cppName = node instanceof IASTName ? (IASTName) node : null; - } - + IASTName cppName = findASTName(binding); QtEnumName astName = new QtEnumName(qobjName, refName, name, cppName, location, isFlag); symbols.add(owner, astName, qobjName); @@ -173,7 +174,9 @@ public class QtASTVisitor extends ASTVisitor { Map flagAliases = new HashMap(); for (IASTPreprocessorMacroExpansion expansion : expansions) { - String macroName = String.valueOf(expansion.getMacroReference()); + + IASTName name = expansion.getMacroReference(); + String macroName = name == null ? null : name.toString(); if (QtKeywords.Q_OBJECT.equals(macroName)) continue; @@ -199,6 +202,9 @@ public class QtASTVisitor extends ASTVisitor { handleQPropertyDefn(owner, qobjName, expansion); } + // Process the slot, signal, and invokable method declarations. + extractQMethods(owner, spec, qobjName); + for(EnumDecl decl : enumDecls) decl.handle(owner, spec, qobjName, flagAliases); } @@ -386,4 +392,63 @@ public class QtASTVisitor extends ASTVisitor { } public static final AttrValue None = new AttrValue(0, null); } + + private void extractQMethods(IASTPreprocessorIncludeStatement owner, ICPPASTCompositeTypeSpecifier spec, QObjectName qobjName) { + QtASTClass qtASTClass = QtASTClass.create(spec); + for (IASTDeclaration decl : spec.getMembers()) { + + // We only care about this node if it is within a signal/slot region or if it + // has been tagged with a Qt annotating tag. + int offset = decl.getFileLocation().getNodeOffset(); + IQMethod.Kind kind = qtASTClass.getKindFor(offset); + Long revision = qtASTClass.getRevisionFor(offset); + if (kind == IQMethod.Kind.Unspecified) + continue; + + // Only named methods are processed, so skip this node if it is not a function or + // if it does not have a name. + IASTSimpleDeclaration simpleDecl = getSimpleDecl(decl); + if (simpleDecl == null) + continue; + + ICPPASTFunctionDeclarator decltor = null; + for(IASTDeclarator d : simpleDecl.getDeclarators()) + if (d instanceof ICPPASTFunctionDeclarator) { + decltor = (ICPPASTFunctionDeclarator) d; + break; + } + if (decltor == null) + continue; + + IASTName cppName = decltor.getName(); + if (cppName == null) + continue; + + String qtEncSignatures = QtMethodUtil.getEncodedQtMethodSignatures(decltor); + symbols.add(owner, new QMethodName(qobjName, cppName, kind, qtEncSignatures, revision), qobjName); + } + } + + private static IASTSimpleDeclaration getSimpleDecl(IASTNode node) { + while (node != null && !(node instanceof IASTSimpleDeclaration)) + 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/internal/core/pdom/QtEnumName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtEnumName.java index fa766ffb697..8827fb0af3e 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtEnumName.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtEnumName.java @@ -10,7 +10,7 @@ package org.eclipse.cdt.qt.internal.core.pdom; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.core.runtime.CoreException; -public class QtEnumName extends AbstractQObjectFieldName implements IQtASTName { +public class QtEnumName extends AbstractQObjectMemberName implements IQtASTName { private final IASTName cppEnumName; private final boolean isFlag; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMBinding.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMBinding.java index 293b797f216..d06054aefec 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMBinding.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMBinding.java @@ -8,7 +8,6 @@ package org.eclipse.cdt.qt.internal.core.pdom; import org.eclipse.cdt.core.dom.ast.IASTName; -import org.eclipse.cdt.internal.core.pdom.db.Database; import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding; import org.eclipse.cdt.internal.core.pdom.dom.PDOMLinkage; import org.eclipse.cdt.internal.core.pdom.dom.PDOMNode; @@ -17,9 +16,12 @@ import org.eclipse.core.runtime.CoreException; @SuppressWarnings("restriction") public abstract class QtPDOMBinding extends PDOMBinding { + // The offsetInitializer is initialized with the size of the parent. It is incremented + // during loading of the Fields enum. This value does not reliably store the size of + // the QtPDOMBinding record because the enum will not be initialized until it is needed. + // The record size is retrieved as the offset of the special terminal enumerator Last. private static int offsetInitializer = RECORD_SIZE; protected static enum Field { - CppRecord(Database.PTR_SIZE), Last(0); public final int offset; @@ -46,28 +48,6 @@ public abstract class QtPDOMBinding extends PDOMBinding { protected int getRecordSize() { return Field.Last.offset; } -// -// public long getCppRecord() { -// try { -// return getDB().getRecPtr(Field.CppRecord.getRecord(record)); -// } catch (CoreException e) { -// QtPlugin.log(e); -// } -// -// return 0; -// } -// -// public IBinding getCppBinding() throws CoreException { -// long cppRec = getCppRecord(); -// if (cppRec == 0) -// return null; -// -// PDOMLinkage cppLinkage = getPDOM().getLinkage(ILinkage.CPP_LINKAGE_ID); -// if (cppLinkage == null) -// return null; -// -// return cppLinkage.getBinding(cppRec); -// } protected QtPDOMLinkage getQtLinkage() { PDOMLinkage pdomLinkage = getLinkage(); diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMNodeType.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMNodeType.java index ab4f4ec2649..86734c22aa1 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMNodeType.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMNodeType.java @@ -17,7 +17,7 @@ public enum QtPDOMNodeType { QObject, QEnum, QProperty, - QPropertyAttribute; + QMethod; public final int Type = IIndexBindingConstants.LAST_CONSTANT + 1 + ordinal(); @@ -28,7 +28,7 @@ public enum QtPDOMNodeType { *

* The version is needed because ordinals for these enumerators are written to the file. */ - public static final int VERSION = 3; + public static final int VERSION = 4; public static QtPDOMNodeType forType(int version, int type) { // Nothing has been deleted or replaced yet, so the version is ignored. @@ -52,6 +52,8 @@ public enum QtPDOMNodeType { return new QtPDOMQEnum(linkage, record); case QProperty: return new QtPDOMProperty(linkage, record); + case QMethod: + return new QtPDOMQMethod(linkage, record); } return null; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMProperty.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMProperty.java index a414295a5e5..48823bab01f 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMProperty.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMProperty.java @@ -7,11 +7,6 @@ */ package org.eclipse.cdt.qt.internal.core.pdom; -import org.eclipse.cdt.core.dom.ast.IBinding; -import org.eclipse.cdt.core.dom.ast.ICompositeType; -import org.eclipse.cdt.core.dom.ast.IField; -import org.eclipse.cdt.core.dom.ast.IType; -import org.eclipse.cdt.core.dom.ast.IValue; import org.eclipse.cdt.internal.core.pdom.db.Database; import org.eclipse.cdt.internal.core.pdom.db.IString; import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding; @@ -20,7 +15,7 @@ import org.eclipse.cdt.qt.core.index.IQProperty; import org.eclipse.core.runtime.CoreException; @SuppressWarnings("restriction") -public class QtPDOMProperty extends QtPDOMBinding implements IField { +public class QtPDOMProperty extends QtPDOMBinding { private static int offsetInitializer = QtPDOMBinding.Field.Last.offset; protected static enum Field { @@ -40,8 +35,6 @@ public class QtPDOMProperty extends QtPDOMBinding implements IField { } } - private QtPDOMQObject qobj; - public QtPDOMProperty(QtPDOMLinkage linkage, long record) { super(linkage, record); } @@ -51,12 +44,8 @@ public class QtPDOMProperty extends QtPDOMBinding implements IField { setType(qtName.getType()); - if (!(parent instanceof QtPDOMQObject)) - this.qobj = null; - else { - this.qobj = (QtPDOMQObject) parent; - this.qobj.addChild(this); - } + if (parent instanceof QtPDOMQObject) + ((QtPDOMQObject) parent).addChild(this); } @Override @@ -93,8 +82,8 @@ public class QtPDOMProperty extends QtPDOMBinding implements IField { getDB().putRecPtr(Field.Type.getRecord(record), getDB().newString(type).getRecord()); } - // TODO IType? - public String getTypeStr() throws CoreException { + // IType? + public String getType() throws CoreException { long rec = getDB().getRecPtr(Field.Type.getRecord(record)); if (rec == 0) return null; @@ -115,66 +104,6 @@ public class QtPDOMProperty extends QtPDOMBinding implements IField { return pdomArray.get(); } - @Override - public ICompositeType getCompositeTypeOwner() { - if (qobj == null) - try { - IBinding parent = getParentBinding(); - if (parent instanceof QtPDOMQObject) - qobj = (QtPDOMQObject) parent; - } catch(CoreException e) { - QtPlugin.log(e); - } - - return qobj; - } - - /** - * TODO use the real type? - */ - private static final IType Type = new IType() { - @Override - public Object clone() { - // This is a stateless singleton instance, there is nothing to clone. - return this; - } - - @Override - public boolean isSameType(IType type) { - return type == this; - } - }; - - @Override - public IType getType() { - return Type; - } - - @Override - public IValue getInitialValue() { - return null; - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public boolean isExtern() { - return false; - } - - @Override - public boolean isAuto() { - return false; - } - - @Override - public boolean isRegister() { - return false; - } - public static class Attribute { public final IQProperty.Attribute attr; public final String value; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQEnum.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQEnum.java index 753f5f0c257..ddf2e974278 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQEnum.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQEnum.java @@ -13,21 +13,15 @@ import java.util.List; import org.eclipse.cdt.core.dom.ILinkage; import org.eclipse.cdt.core.dom.ast.IASTName; -import org.eclipse.cdt.core.dom.ast.IBinding; -import org.eclipse.cdt.core.dom.ast.ICompositeType; import org.eclipse.cdt.core.dom.ast.IEnumeration; import org.eclipse.cdt.core.dom.ast.IEnumerator; -import org.eclipse.cdt.core.dom.ast.IField; -import org.eclipse.cdt.core.dom.ast.IType; -import org.eclipse.cdt.core.dom.ast.IValue; import org.eclipse.cdt.internal.core.pdom.db.Database; import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding; import org.eclipse.cdt.internal.core.pdom.dom.PDOMLinkage; -import org.eclipse.cdt.qt.core.QtPlugin; import org.eclipse.core.runtime.CoreException; @SuppressWarnings("restriction") -public class QtPDOMQEnum extends QtPDOMBinding implements IField { +public class QtPDOMQEnum extends QtPDOMBinding { private static int offsetInitializer = QtPDOMBinding.Field.Last.offset; protected static enum Field { @@ -112,68 +106,8 @@ public class QtPDOMQEnum extends QtPDOMBinding implements IField { return QtPDOMNodeType.QEnum.Type; } - @Override - public ICompositeType getCompositeTypeOwner() { - if (qobj == null) - try { - IBinding parent = getParentBinding(); - if (parent instanceof QtPDOMQObject) - qobj = (QtPDOMQObject) parent; - } catch(CoreException e) { - QtPlugin.log(e); - } - - return qobj; - } - public List getEnumerators() throws CoreException { IEnumeration cppEnum = getCppEnumeration(); return cppEnum == null ? Collections.emptyList() : Arrays.asList(cppEnum.getEnumerators()); } - - /** - * A singleton that is used as the type for all instances of the QtEnum. - */ - private static final IType Type = new IType() { - @Override - public Object clone() { - // This is a stateless singleton instance, there is nothing to clone. - return this; - } - - @Override - public boolean isSameType(IType type) { - return type == this; - } - }; - - @Override - public IType getType() { - return Type; - } - - @Override - public IValue getInitialValue() { - return null; - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public boolean isExtern() { - return false; - } - - @Override - public boolean isAuto() { - return false; - } - - @Override - public boolean isRegister() { - return false; - } } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQMethod.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQMethod.java new file mode 100644 index 00000000000..98657eadf4c --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQMethod.java @@ -0,0 +1,115 @@ +/* + * 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.qt.internal.core.pdom; + +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.internal.core.pdom.db.Database; +import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding; +import org.eclipse.cdt.qt.core.index.IQMethod; +import org.eclipse.core.runtime.CoreException; + +@SuppressWarnings("restriction") +public class QtPDOMQMethod extends QtPDOMBinding { + + private static int offsetInitializer = QtPDOMBinding.Field.Last.offset; + protected static enum Field { + Signature(Database.PTR_SIZE), + Revision(8), + Flags(1), + Last(0); + + public final int offset; + + private Field(int sizeof) { + this.offset = offsetInitializer; + offsetInitializer += sizeof; + } + + public long getRecord(long baseRec) { + return baseRec + offset; + } + } + + private static final int KIND_IS_INVOKABLE = 1; + private static final int KIND_IS_SIGNAL = 2; + private static final int KIND_IS_SLOT = 3; + private static final int KIND_MASK = 3; + private static final int HAS_REVISION = 4; + + public QtPDOMQMethod(QtPDOMLinkage linkage, long record) throws CoreException { + super(linkage, record); + } + + public QtPDOMQMethod(QtPDOMLinkage linkage, PDOMBinding parent, IASTName qtName, IASTName cppName, IQMethod.Kind kind, String qtEncSignatures, Long revision) throws CoreException { + super(linkage, parent, qtName); + + byte flag = 0; + switch(kind) { + case Invokable: + flag |= KIND_IS_INVOKABLE; + break; + case Signal: + flag |= KIND_IS_SIGNAL; + break; + case Slot: + flag |= KIND_IS_SLOT; + break; + case Unspecified: + break; + } + + if (revision != null) { + flag |= HAS_REVISION; + getDB().putLong(Field.Revision.getRecord(record), revision.longValue()); + } + + getDB().putByte(Field.Flags.getRecord(record), flag); + + long rec = qtEncSignatures == null ? 0 : getDB().newString(qtEncSignatures).getRecord(); + getDB().putRecPtr(Field.Signature.getRecord(record), rec); + + if (parent instanceof QtPDOMQObject) + ((QtPDOMQObject) parent).addChild(this); + } + + @Override + protected int getRecordSize() { + return Field.Last.offset; + } + + public IQMethod.Kind getKind() throws CoreException { + switch(getDB().getByte(Field.Flags.getRecord(record)) & KIND_MASK) { + case KIND_IS_INVOKABLE: + return IQMethod.Kind.Invokable; + case KIND_IS_SIGNAL: + return IQMethod.Kind.Signal; + case KIND_IS_SLOT: + return IQMethod.Kind.Slot; + default: + return IQMethod.Kind.Unspecified; + } + } + + public String getQtEncodedSignatures() throws CoreException { + long rec = getDB().getRecPtr(Field.Signature.getRecord(record)); + return rec == 0 ? null : getDB().getString(rec).getString(); + } + + public Long getRevision() throws CoreException { + byte flag = getDB().getByte(Field.Flags.getRecord(record)); + if ((flag & HAS_REVISION) == 0) + return null; + + return Long.valueOf(getDB().getLong(Field.Revision.getRecord(record))); + } + + @Override + public int getNodeType() { + return QtPDOMNodeType.QMethod.Type; + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQObject.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQObject.java index 675b053d27a..e8c0e54026e 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQObject.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPDOMQObject.java @@ -17,10 +17,6 @@ import java.util.Map; import org.eclipse.cdt.core.dom.ILinkage; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IBinding; -import org.eclipse.cdt.core.dom.ast.ICompositeType; -import org.eclipse.cdt.core.dom.ast.IField; -import org.eclipse.cdt.core.dom.ast.IScope; -import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase; import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; import org.eclipse.cdt.internal.core.pdom.db.Database; @@ -36,18 +32,13 @@ import org.eclipse.core.runtime.CoreException; * The persisted form of QObjects. */ @SuppressWarnings("restriction") -public class QtPDOMQObject extends QtPDOMBinding implements ICompositeType { +public class QtPDOMQObject extends QtPDOMBinding { - // The RecordSize is initialized with the size of the parent. It is incremented during - // loading of the Fields enum. This value does not reliably store the size of the - // QtPDOMQObject record because the enum will not be initialized until it is needed. - // The record size is retrieved as the offset of the special terminal enumerator Last. private static int offsetInitializer = QtPDOMBinding.Field.Last.offset; protected static enum Field { CppRecord(Database.PTR_SIZE, 3), Children(4 /* From PDOMNodeLinkedList.RECORD_SIZE, which is protected */, 0), ClassInfos(Database.PTR_SIZE, 2), - Properties(Database.PTR_SIZE, 3), Last(0, 0); private final int offset; @@ -115,12 +106,6 @@ public class QtPDOMQObject extends QtPDOMBinding implements ICompositeType { return null; PDOMBinding cppBinding = cppLinkage.getBinding(cppRec); - - // TODO - if (cppBinding == null) - return null; - cppBinding.getAdapter(ICPPClassType.class); - return cppBinding instanceof ICPPClassType ? (ICPPClassType) cppBinding : null; } @@ -208,35 +193,12 @@ public class QtPDOMQObject extends QtPDOMBinding implements ICompositeType { return bases; } - @Override - public boolean isSameType(IType type) { - if (type == this) - return true; - - if (!(type instanceof QtPDOMQObject)) - return false; - - QtPDOMQObject other = (QtPDOMQObject) type; - return getRecord() == other.getRecord() - && getLinkage().equals(other.getLinkage()); - } - - @Override - public int getKey() { - return ICPPClassType.k_class; - } - - @Override - public boolean isAnonymous() { - return false; - } - @Override public void addChild(PDOMNode child) throws CoreException { children.addMember(child); } - public List getFields(Class cls) throws CoreException { + public List getChildren(Class cls) throws CoreException { QtPDOMVisitor.All collector = new QtPDOMVisitor.All(cls); try { children.accept(collector); @@ -248,47 +210,6 @@ public class QtPDOMQObject extends QtPDOMBinding implements ICompositeType { return collector.list; } - @Override - public IField[] getFields() { - QtPDOMVisitor.All collector = new QtPDOMVisitor.All(IField.class); - try { - children.accept(collector); - } catch(CoreException e) { - QtPlugin.log(e); - return IField.EMPTY_FIELD_ARRAY; - } - - return collector.list.toArray(new IField[collector.list.size()]); - } - - @Override - public IField findField(String name) { - QtPDOMVisitor.IFilter filter = new QtPDOMVisitor.PDOMNamedNodeFilter(name); - QtPDOMVisitor.Find finder = new QtPDOMVisitor.Find(IField.class, filter); - try { - accept(finder); - } catch(CoreException e) { - QtPlugin.log(e); - } - return finder.element; - } - - @Override - public IScope getCompositeScope() { - try { - return getCppClassType().getCompositeScope(); - } catch(CoreException e) { - QtPlugin.log(e); - } - - return null; - } - - @Override - public Object clone() { - throw new UnsupportedOperationException(); - } - private static class ClassInfo { public final String key; public final String value; diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPropertyName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPropertyName.java index 5f3ce6ff806..36b19fef9e7 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPropertyName.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/pdom/QtPropertyName.java @@ -17,9 +17,10 @@ import org.eclipse.cdt.qt.core.index.IQProperty; import org.eclipse.core.runtime.CoreException; @SuppressWarnings("restriction") -public class QtPropertyName extends AbstractQObjectFieldName implements IQtASTName { +public class QtPropertyName extends AbstractQObjectMemberName implements IQtASTName { private String type; + // TODO The PDOM attrs should only be created in #createPDOMBinding private List attributes = new ArrayList(); public QtPropertyName(QObjectName qobjName, IASTName ast, String name, QtASTImageLocation location) { diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java index 55a6c6f45c3..fb9ee92b740 100644 --- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java +++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java @@ -17,6 +17,7 @@ public class AllQtTests extends TestSuite { new TestSuite( SimpleTests.class, QObjectTests.class, - QtIndexTests.class); + QtIndexTests.class, + QtRegressionTests.class); } } 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 cd5ea297696..5a3dfcd8071 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 @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Set; import org.eclipse.cdt.qt.core.index.IQEnum; +import org.eclipse.cdt.qt.core.index.IQMethod; import org.eclipse.cdt.qt.core.index.IQObject; import org.eclipse.cdt.qt.core.index.IQProperty; import org.eclipse.cdt.qt.core.index.IQProperty.Attribute; @@ -415,4 +416,106 @@ public class QObjectTests extends BaseQtTestCase { } assertTrue("missing properties " + missingAttrs.toString(), missingAttrs.length() == 0); } + + // #include "junit-QObject.hh" + // class Q : public QObject + // { + // Q_OBJECT + // signals: + // public: void notASignal(); + // Q_SIGNALS: void signal(); + // public: void notAnotherSignal(); + // Q_SIGNAL void anotherSignal(); + // }; + public void testSimpleSignal() throws Exception { + loadComment("simple_signal.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + IQObject.IMembers signals = qobj.getSignals(); + assertNotNull(signals); + + Collection locals = signals.locals(); + assertNotNull(locals); + + Iterator i = locals.iterator(); + assertTrue(i.hasNext()); + assert_checkQMethod(i.next(), qobj, "signal", IQMethod.Kind.Signal, null); + assertTrue(i.hasNext()); + assert_checkQMethod(i.next(), qobj, "anotherSignal", IQMethod.Kind.Signal, null); + assertFalse(i.hasNext()); + } + + // #include "junit-QObject.hh" + // template class QList {}; + // class QString {}; + // class Q : public QObject + // { + // Q_OBJECT + // + // // From the QML test suite -- this is not valid C++. The Qt moc generates duplicate const, + // // but our CDT-based implementation is not able to do the same. Instead we generate what + // // would be the correct C++ signature. + // Q_INVOKABLE void someFunc(const QList const &p1, QString p2 = "Hello"); + // + // // variations on the above + // Q_INVOKABLE void someFunc1(const QList &p1, QString p2 = "Hello"); + // Q_INVOKABLE void someFunc2(QList const &p1, QString p2 = "Hello"); + // Q_INVOKABLE void someFunc3(const QList &p1, QString p2 = "Hello"); + // Q_INVOKABLE void someFunc4(const QList &p1, QString p2 = "Hello"); + // Q_INVOKABLE void someFunc5(const QList &p1, QString p2 = "Hello") const; + // Q_INVOKABLE void someFunc6(const QList &p1, QString p2 = "Hello"); + // }; + public void testInvokables() throws Exception { + loadComment("invokables.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + IQObject.IMembers invokables = qobj.getInvokables(); + assertNotNull(invokables); + assertEquals(7, invokables.locals().size()); + + for(IQMethod invokable : invokables.locals()) { + + assertTrue(invokable.getName(), qobj == invokable.getOwner()); + assertEquals(invokable.getName(), IQMethod.Kind.Invokable, invokable.getKind()); + assertNull(invokable.getRevision()); + + if ("someFunc".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc(QList,QString)")); + else if ("someFunc1".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc1(QList,QString)")); + else if ("someFunc2".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc2(QList,QString)")); + else if ("someFunc3".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc3(QList,QString)")); + else if ("someFunc4".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc4(QList,QString)")); + else if ("someFunc5".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc5(QList,QString)")); + else if ("someFunc6".equals(invokable.getName())) + assertTrue(invokable.getSignatures().contains("someFunc6(QList,QString)")); + else + fail("unexpected invokable " + invokable.getName()); + } + } + + 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()); + } } 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 new file mode 100644 index 00000000000..bf168b7c588 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtRegressionTests.java @@ -0,0 +1,337 @@ +/* + * 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.qt.tests; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.cdt.qt.core.index.IQMethod; +import org.eclipse.cdt.qt.core.index.IQObject; +import org.eclipse.cdt.qt.core.index.QtIndex; + +public class QtRegressionTests extends BaseQtTestCase { + + private static Map> buildExpectedMap(String mocOutput) { + Map> expected = new HashMap>(); + for(String moc_signature : mocOutput.split("\0")) { + String name = moc_signature.split("\\(")[0]; + Set set = expected.get(name); + if (set == null) { + set = new HashSet(); + expected.put(name, set); + } + set.add(moc_signature); + } + + return expected; + } + + // #include "junit-QObject.hh" + // struct T {}; + // class Q : public QObject + // { + // Q_OBJECT + // public: + // void func(); + // signals: + // void sig_int(int i = 5); + // void sig_const_int(const int i = 5); + // void sig_T(T * t = 0); + // void sig_const_T(const T * const t = 0); + // }; + public void testDefaultParameters() throws Exception { + loadComment("defaultParams.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + // Based on the moc output, but modified to manage our handling for default parameters. The + // moc generates two signatures, sig(N::TS::M) and sig(), we just mark optional parameters + // with a trailing =. However, QMethod#getSignature is currently modified to strip the + // default value indication. So, we're only dealing with the full signature here. + String moc = "sig_int(int)\0sig_int()\0sig_const_int(int)\0" + + "sig_const_int()\0sig_T(T*)\0sig_T()\0" + + "sig_const_T(T*const)\0sig_const_T()\0"; + Map> expected = buildExpectedMap(moc); + + IQObject.IMembers sigs = qobj.getSignals(); + assertNotNull(sigs); + Collection locals = sigs.locals(); + assertNotNull(locals); + for(IQMethod method : locals) { + Set set = expected.get(method.getName()); + assertNotNull("unexpected method " + method.getName() + " (" + method.getSignatures() + ')', set); + for(String signature : method.getSignatures()) { + assertTrue(set.remove(signature)); + } + assertTrue("did not find all signatures for " + method.getName(), set.isEmpty()); + expected.remove(method.getName()); + } + assertEquals(0, expected.size()); + } + + // #include "junit-QObject.hh" + // typedef int Tl1; + // typedef Tl1 Tl2; + // enum _E {}; + // struct S { typedef int M; typedef char Mb; }; + // template struct S_TEMPLATE { }; + // namespace N + // { + // typedef int Ib; + // typedef _E Eb; + // enum E {}; + // namespace N2 { enum E2 {}; typedef E2 TE2; } + // typedef E TEa; + // typedef TEa TEb; + // typedef N2::E2 N2_E2; + // typedef N2::TE2 N2_TE2; + // typedef S TS; + // } + // typedef N::E N_E; + // namespace N2 { typedef N::Ib Ic; } + // class Q : public QObject + // { + // Q_OBJECT + // public: + // void func(); + // signals: + // void sig_int(int); + // void sig_Tl1(Tl1); + // void sig_Tl2(Tl2); + // void sig_int_ptr(int *); + // void sig_Tl1_ptr(Tl1 *); + // void sig_Tl2_ptr(Tl2 *); + // + // void sig_qual1(N::E); + // void sig_qual2(N::N2::E2); + // void sig_typedef1(N_E); + // void sig_typedef2(N::TEa); + // void sig_typedef3(N::TEb); + // void sig_typedef4(N::N2_E2); + // void sig_typedef5(N::N2_TE2); + // void sig_typedef6(N::N2::TE2); + // + // void sig_nested1(S::Mb); + // void sig_nested2(N::TS::M); + // void sig_nested3(N::Ib); + // void sig_nested4(N::Eb); + // void sig_nested5(N2::Ic); + // + // void sig_S ( S ); // sig_S ( S ) + // void const_S (const S ); // const_S ( S ) + // void S_const ( S const ); // S_const ( S ) + // void S_ref ( S & ); // S_ref ( S & ) + // void const_S_ref (const S & ); // const_S_ref ( S ) + // void S_const_ref( S const &); // S_const_ref( S ) + // void S_ptr ( S * ); + // void S_ptr_const( S * const); + // void const_S_ptr (const S * ); + // void const_S_ptr_const(const S * const); + // void const_S_ptr_const_def(const S * const s = 0); + // void S_ptr_ref ( S * &); + // void S_ptr_const_ref( S * const &); + // void const_S_ptr_const_ref(const S * const &); + // void S_ptr_ptr ( S * *); + // void S_ptr_const_ptr( S * const *); + // void const_S_ptr_ptr (const S * *); + // void const_S_ptr_const_ptr(const S * const *); + // void S_ptr_ptr_const ( S * * const); + // void S_ptr_const_ptr_const( S * const * const); + // void const_S_ptr_ptr_const (const S * * const); + // void const_S_ptr_const_ptr_const(const S * const * const); + // + // void S_template_1(const S_TEMPLATE & p); + // void S_template_2(const S_TEMPLATE & p); + // void S_template_3(S_TEMPLATE const & p); + // void S_template_4(S_TEMPLATE const & p); + // void S_template_X(const S_TEMPLATE const & p); + // }; + public void testBug338930() throws Exception { + loadComment("bug338930.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + // Copy and pasted moc output (signals only) to make sure we're getting an exact match. + String moc_output + = "sig_int(int)\0" + + "sig_Tl1(Tl1)\0sig_Tl2(Tl2)\0sig_int_ptr(int*)\0" + + "sig_Tl1_ptr(Tl1*)\0sig_Tl2_ptr(Tl2*)\0" + + "sig_qual1(N::E)\0sig_qual2(N::N2::E2)\0" + + "sig_typedef1(N_E)\0sig_typedef2(N::TEa)\0" + + "sig_typedef3(N::TEb)\0sig_typedef4(N::N2_E2)\0" + + "sig_typedef5(N::N2_TE2)\0" + + "sig_typedef6(N::N2::TE2)\0sig_nested1(S::Mb)\0" + + "sig_nested2(N::TS::M)\0sig_nested3(N::Ib)\0" + + "sig_nested4(N::Eb)\0sig_nested5(N2::Ic)\0" + + + "sig_S(S)\0const_S(S)\0S_const(S)\0S_ref(S&)\0" + + "const_S_ref(S)\0S_const_ref(S)\0S_ptr(S*)\0" + + "S_ptr_const(S*const)\0const_S_ptr(const S*)\0" + + "const_S_ptr_const(S*const)\0const_S_ptr_const_def(S*const)\0" + + "const_S_ptr_const_def()\0S_ptr_ref(S*&)\0" + + "S_ptr_const_ref(S*)\0const_S_ptr_const_ref(S*const)\0" + + "S_ptr_ptr(S**)\0S_ptr_const_ptr(S*const*)\0" + + "const_S_ptr_ptr(const S**)\0" + + "const_S_ptr_const_ptr(const S*const*)\0" + + "S_ptr_ptr_const(S**const)\0" + + "S_ptr_const_ptr_const(S*const*const)\0" + + "const_S_ptr_ptr_const(S**const)\0" + + "const_S_ptr_const_ptr_const(S*const*const)\0" + + "S_template_1(S_TEMPLATE)\0" + + "S_template_2(S_TEMPLATE)\0" + + "S_template_3(S_TEMPLATE)\0" + + "S_template_4(S_TEMPLATE)\0" + + "S_template_X(S_TEMPLATE)"; + + Map> expected = buildExpectedMap(moc_output); + + IQObject.IMembers sigs = qobj.getSignals(); + assertNotNull(sigs); + Collection locals = sigs.locals(); + assertNotNull(locals); + for(IQMethod method : locals) { + Set set = expected.get(method.getName()); + assertNotNull("unexpected signal " + method.getName() + " (" + method.getSignatures() + ')', set); + for(String signature : method.getSignatures()) + assertTrue(set.remove(signature)); + assertTrue("did not find all signatures for " + method.getName(), set.isEmpty()); + expected.remove(method.getName()); + } + assertEquals(0, expected.size()); + } + + // #include "junit-QObject.hh" + // class Q : public QObject + // { + // Q_OBJECT + // Q_SLOT void const_ref(const QString &); + // Q_SLOT void const_val(const QString ); + // Q_SLOT void reference( QString &); + // Q_SLOT void value( QString ); + // enum E { }; + // Q_SIGNAL void signalEnum_const_ref(const E &); + // Q_SIGNAL void signalEnum_reference(E &); + // Q_SIGNAL void signalEnum_qualified(Q::E); + // void func() + // { + // connect(this, SIGNAL(destroyed(QObject*), this, SLOT(const_ref(QString)))); + // connect(this, SIGNAL(destroyed(QObject*), this, SLOT(const_val(QString)))); + // connect(this, SIGNAL(signalEnum_const_ref(E), this, SLOT(reference(QString&)))); + // connect(this, SIGNAL(signalEnum_reference(E&), this, SLOT(value(QString)))); + // } + // }; + public void testBug344931() throws Exception { + loadComment("bug344931.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + IQObject.IMembers slotMembers = qobj.getSlots(); + assertNotNull(slotMembers); + + Collection slots = slotMembers.locals(); + assertNotNull(slots); + assertEquals(4, slots.size()); + + for(IQMethod slot : slots) { + if ("const_ref".equals(slot.getName())) + assertTrue(slot.getSignatures().contains("const_ref(QString)")); + else if ("const_val".equals(slot.getName())) + assertTrue(slot.getSignatures().contains("const_val(QString)")); + else if ("reference".equals(slot.getName())) + assertTrue(slot.getSignatures().contains("reference(QString&)")); + else if ("value".equals(slot.getName())) + assertTrue(slot.getSignatures().contains("value(QString)")); + else + fail("unexpected slot " + slot.getName()); + } + + IQObject.IMembers signalMembers = qobj.getSignals(); + assertNotNull(signalMembers); + + Collection signals = signalMembers.locals(); + assertNotNull(signals); + assertEquals(3, signals.size()); + + for(IQMethod signal : signals) { + if ("signalEnum_const_ref".equals(signal.getName())) + assertTrue(signal.getSignatures().contains("signalEnum_const_ref(E)")); + else if ("signalEnum_reference".equals(signal.getName())) + assertTrue(signal.getSignatures().contains("signalEnum_reference(E&)")); + else if ("signalEnum_qualified".equals(signal.getName())) + assertTrue(signal.getSignatures().contains("signalEnum_qualified(Q::E)")); + else + fail("unexpected signal " + signal.getName()); + } + } + + // #include "junit-QObject.hh" + // class Q : public QObject + // { + // Q_OBJECT + // public: + // void func(); + // private slots: + // void slot1(); + // private: + // Q_SLOT void slot2(); + // Q_SLOT void slot3(); + // }; + // void Q::slot1() { } + // void Q::slot2() { } + // void Q::func() + // { + // QObject::connect( this, destroyed( QObject * ), this, slot1() ); + // QObject::connect( this, destroyed( QObject * ), this, slot2() ); + // QObject::connect( this, destroyed( QObject * ), this, slot3() ); + // } + public void testSlotDefn() throws Exception { + loadComment("slotDefn.hh"); + + QtIndex qtIndex = QtIndex.getIndex(fProject); + assertNotNull(qtIndex); + + IQObject qobj = qtIndex.findQObject(new String[]{ "Q" }); + if (!isIndexOk("Q", qobj)) + return; + assertNotNull(qobj); + + IQObject.IMembers slots = qobj.getSlots(); + assertNotNull(slots); + Collection localSlots = slots.locals(); + assertNotNull(localSlots); + + // make sure that the three slot functions are found, but none of the inherited or + // non-slot functions + Set expected = new HashSet(Arrays.asList("slot1", "slot2", "slot3")); + for(IQMethod method : localSlots) + assertTrue("unexpected slot " + method.getName(), expected.remove(method.getName())); + assertEquals("missing slots " + expected.toString(), 0, expected.size()); + } +}