| /* |
| * Copyright (c) 2014, 2015 QNX Software Systems and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| */ |
| package org.eclipse.cdt.internal.qt.core; |
| |
| import java.util.regex.Matcher; |
| |
| import org.eclipse.cdt.core.dom.ast.DOMException; |
| import org.eclipse.cdt.core.dom.ast.IASTFileLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IASTNode; |
| import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; |
| import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.IType; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; |
| import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics; |
| import org.eclipse.cdt.internal.qt.core.index.IQMethod; |
| import org.eclipse.cdt.internal.qt.core.index.IQObject; |
| import org.eclipse.cdt.internal.qt.core.index.QtIndex; |
| import org.eclipse.cdt.internal.qt.core.pdom.ASTNameReference; |
| import org.eclipse.cdt.internal.qt.core.pdom.QtASTImageLocation; |
| import org.eclipse.core.resources.IProject; |
| |
| /** |
| * Qt signals and slots are referenced using the SIGNAL and SLOT macros. The expansion |
| * parameter is the signature of the signal or slot and they are associated with a type. |
| * This utility class is used to convert from these AST nodes to an IASTName that can be |
| * used as a reference to the IBinding for the C++ method. |
| */ |
| @SuppressWarnings("restriction") |
| public class QtMethodReference extends ASTNameReference { |
| |
| public static enum Type { |
| Signal("sender", "SIGNAL", "signal"), |
| Slot("receiver", "SLOT", "member"); |
| |
| public final String roleName; |
| public final String macroName; |
| public final String paramName; |
| |
| public boolean matches(Type other) { |
| if (other == null) |
| return false; |
| |
| // The signal parameter must be a SIGNAL, but the slot could be a |
| // SLOT or a SIGNAL. |
| return this == Signal ? other == Signal : true; |
| } |
| |
| /** |
| * Return the type of method reference within the expansion of the given macro name. |
| */ |
| public static Type from(IASTName name) { |
| String nameStr = String.valueOf(name); |
| if (QtKeywords.SIGNAL.equals(nameStr)) |
| return Signal; |
| if (QtKeywords.SLOT.equals(nameStr)) |
| return Slot; |
| return null; |
| } |
| |
| private Type(String roleName, String macroName, String paramName) { |
| this.roleName = roleName; |
| this.macroName = macroName; |
| this.paramName = paramName; |
| } |
| } |
| |
| private final Type type; |
| private final ICPPClassType cls; |
| private final String expansionParam; |
| |
| private QtMethodReference(Type type, ICPPClassType cls, IASTName macroRefName, String expansionParam, IASTFileLocation location) { |
| super(macroRefName, location); |
| |
| this.type = type; |
| this.cls = cls; |
| this.expansionParam = expansionParam; |
| } |
| |
| /** |
| * Return the C++ class that defines the Qt method that is being referenced. |
| */ |
| public ICPPClassType getContainingType() { |
| return cls; |
| } |
| |
| /** |
| * Look for SIGNAL or SLOT macro expansions at the location of the given node. Return the |
| * QMethod reference is an expansion is found and null otherwise. |
| * <p> |
| * QMetaMethod references cannot be statically resolved so null will be returned in this case. |
| */ |
| public static QtMethodReference parse(IASTNode parent, IType cppType, IASTNode arg) { |
| if (!(cppType instanceof ICPPClassType) || arg == null) |
| return null; |
| ICPPClassType cls = (ICPPClassType) cppType; |
| |
| // Look for a SIGNAL or SLOT expansion as this location. |
| Type type = null; |
| IASTName macroReferenceName = null; |
| for(IASTNodeLocation location : arg.getNodeLocations()) { |
| if (!(location instanceof IASTMacroExpansionLocation)) |
| continue; |
| |
| IASTPreprocessorMacroExpansion expansion = ((IASTMacroExpansionLocation) location).getExpansion(); |
| macroReferenceName = expansion.getMacroReference(); |
| IASTPreprocessorMacroDefinition macroDefn = expansion.getMacroDefinition(); |
| |
| type = Type.from(macroDefn.getName()); |
| if (type != null) |
| break; |
| } |
| |
| // There is nothing to do if the expected type of expansion is not found. |
| if (macroReferenceName == null || type == null) |
| return null; |
| |
| // This check will miss cases like: |
| // #define MY_SIG1 SIGNAL |
| // #define MY_SIG2(s) SIGNAL(s) |
| // #define MY_SIG3(s) SIGNAL(signal()) |
| // connect( &a, MY_SIG1(signal()), ... |
| // connect( &a, MY_SIG2(signal()), ... |
| // connect( &a, MY_SIG2, ... |
| // This could be improved by adding tests when arg represents a macro expansion. However, I'm |
| // not sure if we would be able to follow the more complicated case of macros that call functions |
| // that use the SIGNAL macro. For now I've implemented the simpler check of forcing the call to |
| // use the SIGNAL/SLOT macro directly. |
| String raw = arg.getRawSignature(); |
| Matcher m = ASTUtil.Regex_MacroExpansion.matcher( raw ); |
| if( ! m.matches() ) |
| return null; |
| |
| // Get the argument to the SIGNAL/SLOT macro and the offset/length of that argument within the |
| // complete function argument. E.g., with this argument to QObject::connect |
| // SIGNAL( signal(int) ) |
| // the values are |
| // expansionArgs: "signal(int)" |
| // expansionOffset: 8 |
| // expansionLength: 11 |
| String expansionArgs = m.group( 2 ); |
| int expansionOffset = m.start( 2 ); |
| int expansionLength = m.end( 2 ) - expansionOffset; |
| |
| IASTFileLocation location = new QtASTImageLocation(macroReferenceName.getFileLocation(), expansionOffset, expansionLength); |
| return new QtMethodReference(type, cls, macroReferenceName, expansionArgs, location); |
| } |
| |
| public Type getType() { |
| return type; |
| } |
| |
| @Override |
| public String getRawSignature() { |
| return expansionParam; |
| } |
| |
| @Override |
| public char[] getSimpleID() { |
| return expansionParam.toCharArray(); |
| } |
| |
| private IQObject findQObject() { |
| String[] qualName = null; |
| try { |
| qualName = cls.getQualifiedName(); |
| } catch(DOMException e) { |
| Activator.log(e); |
| } |
| |
| IProject project = ASTUtil.getProject(delegate); |
| if (project == null) |
| return null; |
| |
| QtIndex qtIndex = QtIndex.getIndex(project); |
| if (qtIndex == null) |
| return null; |
| |
| return qtIndex.findQObject(qualName); |
| } |
| |
| public IQMethod getMethod() { |
| IQObject qobj = findQObject(); |
| if (qobj == null) |
| return null; |
| |
| // Return the first matching method. |
| for(IQMethod method : ASTUtil.findMethods(qobj, this)) |
| return method; |
| |
| return null; |
| } |
| |
| @Override |
| public IBinding resolveBinding() { |
| if (binding != null) |
| return binding; |
| |
| // Qt method references return the C++ method that is being referenced in the SIGNAL or |
| // SLOT macro expansion. |
| String methodName = expansionParam; |
| int paren = methodName.indexOf('('); |
| if (paren > 0) |
| methodName = methodName.substring(0, paren); |
| IBinding[] methods = CPPSemantics.findBindings(cls.getCompositeScope(), methodName.trim(), false); |
| |
| // TODO find the one binding that matches the parameter of the macro expansion |
| // 1) Normalize expansionParam |
| // 2) Use it to filter the matching methods |
| binding = methods.length > 0 ? methods[0] : null; |
| return binding; |
| } |
| } |