1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-06-06 01:06:01 +02:00

Bug 422841: Add IQMethod to the QtIndex

This adds support for Qt slots, signals, and invokables to the QtIndex.

This does not yet generate PDOM references for QObject::connection
function calls and the Content Assistant is not contributed yet.

This also fixes a problem in the GNUCPPSourceParser class (internal to
cdt.core).  The class has a protected method that accepts an inner enum
as a parameter.  That enum was marked as private, meaning the method
could not actually be used by subclasses.  I've updated the enum to
match the visibility of the method.

There are three big areas needed to support Qt methods:

1) Slot and signal regions must be identified in the C++ class
definition.  These regions are introduced with special macros and the
region extends to the next region or to the next visibility label.

   Single methods can also be marked with (different) special macros.
This only applies outside of a slot/signal region.

   I've created QtASTClass which examines the input class spec in order
to identify all such regions.  This information is used when creating
the QtPDOM nodes for these methods.

2) Some places in Qt use type information embedded as macro expansion
parameters.  The values are lost by the standard C++ parser (since they
are just text in the expansion).  I've added an extension to the
GNUCPPSourceParser that accepts an input string (the macro expansion
parameter) and produces an appropriate IASTNode if possible.

3) The Qt moc follows specific and non-standard rules when matching
method signatures inside of QObject::connect function calls.  I've added
a utility that creates the same signature using the CDT AST as input.

   I learned the rules used by the moc by observing it's output for
significant cases.  Those cases have been put into a test case that is
included in this patch.

Change-Id: If812558db315abec637653cc974abf1c0c13d95b
Signed-off-by: Andrew Eidsness <eclipse@jfront.com>
Reviewed-on: https://git.eclipse.org/r/19672
Tested-by: Hudson CI
Reviewed-by: Doug Schaefer <dschaefer@qnx.com>
IP-Clean: Doug Schaefer <dschaefer@qnx.com>
This commit is contained in:
Andrew Eidsness 2013-12-10 12:49:29 -05:00 committed by Doug Schaefer
parent 2e0bd2e95b
commit 1b42998e47
25 changed files with 1878 additions and 384 deletions

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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:
* <br>
* GNUCPPSourceParser - CPreprocessor - Lexer
* <p>
* 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:
* <br>
* 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<String, IMacroBinding> 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) {
}
}

View file

@ -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:
* <pre>
* class T : public QObject
* {
* Q_OBJECT
* Q_SIGNAL void signal1( int = 5 );
* };
* </pre>
* This would return "{ signal1(int), signal1() }".
*/
public Collection<String> 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();
}

View file

@ -86,6 +86,21 @@ public interface IQObject extends IQElement {
*/
public List<IQObject> getBases();
/**
* Returns the methods that have been tagged as Qt slots. Does not return null.
*/
public IMembers<IQMethod> getSlots();
/**
* Returns the methods that have been tagged as Qt signals. Does not return null.
*/
public IMembers<IQMethod> getSignals();
/**
* Returns the methods that have been tagged with Q_INVOKABLE. Does not return null.
*/
public IMembers<IQMethod> getInvokables();
/**
* Returns the expansions of the Q_PROPERTY macro. Does not return null.
*/

View file

@ -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<String> 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<String> signatures = new ArrayList<String>();
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('>');
}
}

View file

@ -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);

View file

@ -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<String> 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<String> getSignatures() {
return signatures == null ? Collections.<String>emptyList() : signatures;
}
@Override
public Long getRevision() {
return revision;
}
@Override
public String toString() {
return kind.toString() + ' ' + signatures;
}
}

View file

@ -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<IQObject> bases;
private final IQObject.IMembers<IQMethod> slots;
private final IQObject.IMembers<IQMethod> signals;
private final IQObject.IMembers<IQMethod> invokables;
private final IQObject.IMembers<IQProperty> properties;
private final List<IQEnum> enums;
private final Map<String, String> classInfos;
@ -34,6 +39,9 @@ public class QObject implements IQObject {
this.name = pdomQObject.getName();
this.pdomQObject = pdomQObject;
List<IQMethod> baseSlots = new ArrayList<IQMethod>();
List<IQMethod> baseSignals = new ArrayList<IQMethod>();
List<IQMethod> baseInvokables = new ArrayList<IQMethod>();
List<IQProperty> baseProps = new ArrayList<IQProperty>();
this.bases = new ArrayList<IQObject>();
@ -45,13 +53,35 @@ public class QObject implements IQObject {
this.classInfos = pdomQObject.getClassInfos();
List<IQMethod> slots = new ArrayList<IQMethod>();
List<IQMethod> signals = new ArrayList<IQMethod>();
List<IQMethod> invokables = new ArrayList<IQMethod>();
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<IQEnum>();
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<IQProperty> props = new ArrayList<IQProperty>();
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<IQMethod> getSlots() {
return slots;
}
@Override
public IMembers<IQMethod> getSignals() {
return signals;
}
@Override
public IMembers<IQMethod> getInvokables() {
return invokables;
}
@Override
public IQObject.IMembers<IQProperty> getProperties() {
return properties;

View file

@ -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:
* <pre>
* (:?READ)|(?:WRITE)|(:?RESET)|...
* </pre>
* 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 );
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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.
* <p>
* 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<Region> regions;
private final Iterator<Tag> tags;
private final Iterator<Revision> 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<Tag> tags = new ArrayList<Tag>();
ArrayList<Revision> revisions = new ArrayList<Revision>();
ArrayList<Region> regions = new ArrayList<Region>();
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<Region> 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<Tag> 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<Region> regions, List<Tag> tags, List<Revision> 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();
}
}

View file

@ -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<String, String> flagAliases = new HashMap<String, String>();
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;
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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 {
* <p>
* 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;

View file

@ -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;

View file

@ -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<IEnumerator> getEnumerators() throws CoreException {
IEnumeration cppEnum = getCppEnumeration();
return cppEnum == null ? Collections.<IEnumerator>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;
}
}

View file

@ -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;
}
}

View file

@ -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 <T extends IField> List<T> getFields(Class<T> cls) throws CoreException {
public <T extends QtPDOMBinding> List<T> getChildren(Class<T> cls) throws CoreException {
QtPDOMVisitor.All<T> collector = new QtPDOMVisitor.All<T>(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<IField> collector = new QtPDOMVisitor.All<IField>(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<IField> finder = new QtPDOMVisitor.Find<IField>(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;

View file

@ -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<QtPDOMProperty.Attribute> attributes = new ArrayList<QtPDOMProperty.Attribute>();
public QtPropertyName(QObjectName qobjName, IASTName ast, String name, QtASTImageLocation location) {

View file

@ -17,6 +17,7 @@ public class AllQtTests extends TestSuite {
new TestSuite(
SimpleTests.class,
QObjectTests.class,
QtIndexTests.class);
QtIndexTests.class,
QtRegressionTests.class);
}
}

View file

@ -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<IQMethod> signals = qobj.getSignals();
assertNotNull(signals);
Collection<IQMethod> locals = signals.locals();
assertNotNull(locals);
Iterator<IQMethod> 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 <typename T> 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 QString const*> const &p1, QString p2 = "Hello");
//
// // variations on the above
// Q_INVOKABLE void someFunc1(const QList<const QString const*> &p1, QString p2 = "Hello");
// Q_INVOKABLE void someFunc2(QList<const QString const*> const &p1, QString p2 = "Hello");
// Q_INVOKABLE void someFunc3(const QList<const QString *> &p1, QString p2 = "Hello");
// Q_INVOKABLE void someFunc4(const QList<QString const*> &p1, QString p2 = "Hello");
// Q_INVOKABLE void someFunc5(const QList<const QString *> &p1, QString p2 = "Hello") const;
// Q_INVOKABLE void someFunc6(const QList<QString *const> &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<IQMethod> 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<const QString*>,QString)"));
else if ("someFunc1".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc1(QList<const QString*>,QString)"));
else if ("someFunc2".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc2(QList<const QString*>,QString)"));
else if ("someFunc3".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc3(QList<const QString*>,QString)"));
else if ("someFunc4".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc4(QList<const QString*>,QString)"));
else if ("someFunc5".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc5(QList<const QString*>,QString)"));
else if ("someFunc6".equals(invokable.getName()))
assertTrue(invokable.getSignatures().contains("someFunc6(QList<QString*const>,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());
}
}

View file

@ -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<String, Set<String>> buildExpectedMap(String mocOutput) {
Map<String, Set<String>> expected = new HashMap<String, Set<String>>();
for(String moc_signature : mocOutput.split("\0")) {
String name = moc_signature.split("\\(")[0];
Set<String> set = expected.get(name);
if (set == null) {
set = new HashSet<String>();
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<String, Set<String>> expected = buildExpectedMap(moc);
IQObject.IMembers<IQMethod> sigs = qobj.getSignals();
assertNotNull(sigs);
Collection<IQMethod> locals = sigs.locals();
assertNotNull(locals);
for(IQMethod method : locals) {
Set<String> 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<typename T> 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<const S *> & p);
// void S_template_2(const S_TEMPLATE<S const *> & p);
// void S_template_3(S_TEMPLATE<const S *> const & p);
// void S_template_4(S_TEMPLATE<S const *> const & p);
// void S_template_X(const S_TEMPLATE<const S const *> 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<const S*>)\0"
+ "S_template_2(S_TEMPLATE<const S*>)\0"
+ "S_template_3(S_TEMPLATE<const S*>)\0"
+ "S_template_4(S_TEMPLATE<const S*>)\0"
+ "S_template_X(S_TEMPLATE<const S*>)";
Map<String, Set<String>> expected = buildExpectedMap(moc_output);
IQObject.IMembers<IQMethod> sigs = qobj.getSignals();
assertNotNull(sigs);
Collection<IQMethod> locals = sigs.locals();
assertNotNull(locals);
for(IQMethod method : locals) {
Set<String> 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<IQMethod> slotMembers = qobj.getSlots();
assertNotNull(slotMembers);
Collection<IQMethod> 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<IQMethod> signalMembers = qobj.getSignals();
assertNotNull(signalMembers);
Collection<IQMethod> 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<IQMethod> slots = qobj.getSlots();
assertNotNull(slots);
Collection<IQMethod> localSlots = slots.locals();
assertNotNull(localSlots);
// make sure that the three slot functions are found, but none of the inherited or
// non-slot functions
Set<String> expected = new HashSet<String>(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());
}
}