mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-23 14:42:11 +02:00
Bug 509185 - Completion of constructor call with uniform initialization syntax
Change-Id: I3b0b3c5dd32ee09755e58cdb3dbc6af019ddd650
This commit is contained in:
parent
a395647e48
commit
1c60b844c5
3 changed files with 204 additions and 11 deletions
|
@ -1227,6 +1227,46 @@ public class CompletionTests extends CompletionTestBase {
|
||||||
assertCompletionResults(fCursorOffset, expected, ID);
|
assertCompletionResults(fCursorOffset, expected, ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// struct Waldo {
|
||||||
|
// Waldo(int, int);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// int main() {
|
||||||
|
// Waldo waldo{/*cursor*/}
|
||||||
|
// }
|
||||||
|
public void testUniformInitializationInSimpleDeclaration_509185() throws Exception {
|
||||||
|
final String[] expected = { "Waldo(const Waldo &)", "Waldo(int, int)" };
|
||||||
|
assertCompletionResults(fCursorOffset, expected, ID);
|
||||||
|
assertCompletionResults(new String[] { "", "" }); // No replacements, just context info
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct Waldo {
|
||||||
|
// Waldo(int, int);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// int main() {
|
||||||
|
// auto waldo = Waldo{/*cursor*/}
|
||||||
|
// }
|
||||||
|
public void testUniformInitializationInSimpleTypeConstructorExpression_509185() throws Exception {
|
||||||
|
final String[] expected = { "Waldo(const Waldo &)", "Waldo(int, int)" };
|
||||||
|
assertCompletionResults(fCursorOffset, expected, ID);
|
||||||
|
assertCompletionResults(new String[] { "", "" }); // No replacements, just context info
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct Waldo {
|
||||||
|
// Waldo(int, int);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// struct Finder {
|
||||||
|
// Waldo waldo;
|
||||||
|
// Finder() : waldo{/*cursor*/
|
||||||
|
// };
|
||||||
|
public void testUniformInitializationInConstructorChainInitializer_509185() throws Exception {
|
||||||
|
final String[] expected = { "Waldo(const Waldo &)", "Waldo(int, int)" };
|
||||||
|
assertCompletionResults(fCursorOffset, expected, ID);
|
||||||
|
assertCompletionResults(new String[] { "", "" }); // No replacements, just context info
|
||||||
|
}
|
||||||
|
|
||||||
// struct Waldo {
|
// struct Waldo {
|
||||||
// Waldo(int, int);
|
// Waldo(int, int);
|
||||||
// };
|
// };
|
||||||
|
|
|
@ -24,12 +24,25 @@ import org.eclipse.jface.text.ITextViewer;
|
||||||
import org.eclipse.ui.IEditorPart;
|
import org.eclipse.ui.IEditorPart;
|
||||||
|
|
||||||
import org.eclipse.cdt.core.CCorePlugin;
|
import org.eclipse.cdt.core.CCorePlugin;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.ASTCompletionNode;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException;
|
||||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
|
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTInitializerList;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTNamedTypeSpecifier;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConstructorChainInitializer;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTSimpleTypeConstructorExpression;
|
||||||
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
|
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
|
||||||
import org.eclipse.cdt.core.index.IIndex;
|
import org.eclipse.cdt.core.index.IIndex;
|
||||||
import org.eclipse.cdt.core.index.IIndexManager;
|
import org.eclipse.cdt.core.index.IIndexManager;
|
||||||
import org.eclipse.cdt.core.model.ICProject;
|
import org.eclipse.cdt.core.model.ICProject;
|
||||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||||
|
import org.eclipse.cdt.core.parser.IToken;
|
||||||
import org.eclipse.cdt.ui.CUIPlugin;
|
import org.eclipse.cdt.ui.CUIPlugin;
|
||||||
import org.eclipse.cdt.ui.PreferenceConstants;
|
import org.eclipse.cdt.ui.PreferenceConstants;
|
||||||
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
|
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
|
||||||
|
@ -54,6 +67,12 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
private final boolean fIsCompletion;
|
private final boolean fIsCompletion;
|
||||||
private final boolean fIsAutoActivated;
|
private final boolean fIsAutoActivated;
|
||||||
private IIndex fIndex;
|
private IIndex fIndex;
|
||||||
|
// Whether we are doing completion (false) or just showing context information (true).
|
||||||
|
private boolean fIsContextInformationStyle;
|
||||||
|
// The parse offset is the end of the name we are doing completion on.
|
||||||
|
// Since this name can be adjusted by adjustCompletionNode(), the parse offset
|
||||||
|
// may need a corresponding adjustment, and this stores the adjusted offset.
|
||||||
|
private int fAdjustedParseOffset = -1;
|
||||||
|
|
||||||
private Lazy<Integer> fContextInfoPosition = new Lazy<Integer>() {
|
private Lazy<Integer> fContextInfoPosition = new Lazy<Integer>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,6 +86,19 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
private final Lazy<Integer> fParseOffset = new Lazy<Integer>() {
|
private final Lazy<Integer> fParseOffset = new Lazy<Integer>() {
|
||||||
@Override
|
@Override
|
||||||
protected Integer calculateValue() {
|
protected Integer calculateValue() {
|
||||||
|
int result = doCalculate();
|
||||||
|
if (result != getInvocationOffset()) {
|
||||||
|
// If guessCompletionPosition() chose a parse offset that's different
|
||||||
|
// from the invocation offset, we are proposing completions for a name
|
||||||
|
// that's not right under the cursor, and so we just want to show
|
||||||
|
// context information.
|
||||||
|
fIsContextInformationStyle= true;
|
||||||
|
}
|
||||||
|
fAdjustedParseOffset = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int doCalculate() {
|
||||||
if (fIsCompletion) {
|
if (fIsCompletion) {
|
||||||
return guessCompletionPosition(getInvocationOffset());
|
return guessCompletionPosition(getInvocationOffset());
|
||||||
}
|
}
|
||||||
|
@ -78,6 +110,93 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function for adjustCompletionName().
|
||||||
|
private IASTName getAdjustedCompletionName(IASTName completionName) {
|
||||||
|
if (completionName.getSimpleID().length > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (completionName.getParent() instanceof IASTIdExpression &&
|
||||||
|
completionName.getParent().getParent() instanceof IASTInitializerList) {
|
||||||
|
IASTNode initList = completionName.getParent().getParent();
|
||||||
|
if (initList.getParent() instanceof IASTDeclarator &&
|
||||||
|
initList.getParent().getParent() instanceof IASTSimpleDeclaration) {
|
||||||
|
IASTSimpleDeclaration decl = (IASTSimpleDeclaration) completionName.getParent().getParent()
|
||||||
|
.getParent().getParent();
|
||||||
|
if (decl.getDeclSpecifier() instanceof IASTNamedTypeSpecifier) {
|
||||||
|
return ((IASTNamedTypeSpecifier) decl.getDeclSpecifier()).getName();
|
||||||
|
}
|
||||||
|
} else if (initList.getParent() instanceof ICPPASTSimpleTypeConstructorExpression) {
|
||||||
|
ICPPASTSimpleTypeConstructorExpression expr =
|
||||||
|
(ICPPASTSimpleTypeConstructorExpression) initList.getParent();
|
||||||
|
if (expr.getDeclSpecifier() instanceof IASTNamedTypeSpecifier) {
|
||||||
|
return ((IASTNamedTypeSpecifier) expr.getDeclSpecifier()).getName();
|
||||||
|
}
|
||||||
|
} else if (initList.getParent() instanceof ICPPASTConstructorChainInitializer) {
|
||||||
|
ICPPASTConstructorChainInitializer ctorInit =
|
||||||
|
(ICPPASTConstructorChainInitializer) initList.getParent();
|
||||||
|
return ctorInit.getMemberInitializerId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose a better completion node based on information in the AST.
|
||||||
|
*
|
||||||
|
* Currently, this makes an adjustment in one case: if the completion node is
|
||||||
|
* an empty name at the top level of one of the initializers of an initializer
|
||||||
|
* list that may be a constructor call, the completion name is adjusted to
|
||||||
|
* instead be the name preceding the initializer list (which will either name
|
||||||
|
* the type being constructed, or a variable of that type). This allows us to
|
||||||
|
* offer completions for the constructors of this type.
|
||||||
|
*
|
||||||
|
* Currently we handle initializer lists in three contexts:
|
||||||
|
* 1) simple declaration
|
||||||
|
* 2) simple type constructor expression
|
||||||
|
* 3) constructor chain initializer
|
||||||
|
*
|
||||||
|
* TODO: Handle the additional context of a return-expression:
|
||||||
|
* S foo() {
|
||||||
|
* return {...}; // can invoke S constructor with args inside {...}
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Note that for constructor calls with () rather than {} syntax, we
|
||||||
|
* accomplish the same goal by different means: in getParseOffset(), we choose
|
||||||
|
* a parse offset that will give us the desired completion node to begin with.
|
||||||
|
* We can't do this with the {} syntax, because in getParseOffset() we don't
|
||||||
|
* have an AST yet (the choice is made using CHeuristicScanner), and we cannot
|
||||||
|
* distinguish other uses of {} from the desired ones.
|
||||||
|
*
|
||||||
|
* @param completionName the initial completion name, based on the parse offset
|
||||||
|
* chosen using CHeuristicScanner
|
||||||
|
* @return the adjusted completion name, if different from the initial completion
|
||||||
|
* name, or {@code null}
|
||||||
|
*/
|
||||||
|
private IASTCompletionNode adjustCompletionNode(IASTCompletionNode existing) {
|
||||||
|
IASTName[] names = existing.getNames();
|
||||||
|
// If there are multiple completion names, there is a parser ambiguity
|
||||||
|
// near the completion location. Just bail in this case.
|
||||||
|
if (names.length != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
IASTName completionName = names[0];
|
||||||
|
IASTName newCompletionName = getAdjustedCompletionName(completionName);
|
||||||
|
if (newCompletionName != null) {
|
||||||
|
IToken newCompletionToken = null;
|
||||||
|
try {
|
||||||
|
newCompletionToken = newCompletionName.getSyntax();
|
||||||
|
} catch (ExpansionOverlapsBoundaryException e) {
|
||||||
|
}
|
||||||
|
if (newCompletionToken != null) {
|
||||||
|
ASTCompletionNode newCompletionNode = new ASTCompletionNode(newCompletionToken,
|
||||||
|
existing.getTranslationUnit());
|
||||||
|
newCompletionNode.addName(newCompletionName);
|
||||||
|
return newCompletionNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private final Lazy<IASTCompletionNode> fCN = new Lazy<IASTCompletionNode>() {
|
private final Lazy<IASTCompletionNode> fCN = new Lazy<IASTCompletionNode>() {
|
||||||
@Override
|
@Override
|
||||||
protected IASTCompletionNode calculateValue() {
|
protected IASTCompletionNode calculateValue() {
|
||||||
|
@ -106,7 +225,26 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
int flags = parseNonIndexed ? ITranslationUnit.AST_SKIP_INDEXED_HEADERS : ITranslationUnit.AST_SKIP_ALL_HEADERS;
|
int flags = parseNonIndexed ? ITranslationUnit.AST_SKIP_INDEXED_HEADERS : ITranslationUnit.AST_SKIP_ALL_HEADERS;
|
||||||
flags |= ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT;
|
flags |= ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT;
|
||||||
|
|
||||||
return fTU.value().getCompletionNode(fIndex, flags, offset);
|
IASTCompletionNode result = fTU.value().getCompletionNode(fIndex, flags, offset);
|
||||||
|
if (result != null) {
|
||||||
|
// The initial completion code is determined by the parse offset chosen
|
||||||
|
// in getParseOffset() using CHeuristicScanner. Now that we have an AST,
|
||||||
|
// we may want to use information in the AST to choose a better completion
|
||||||
|
// node.
|
||||||
|
IASTCompletionNode adjusted = adjustCompletionNode(result);
|
||||||
|
if (adjusted != null) {
|
||||||
|
// If we made an adjustment, we just want to show context information.
|
||||||
|
fIsContextInformationStyle = true;
|
||||||
|
result = adjusted;
|
||||||
|
if (adjusted.getNames().length > 0) {
|
||||||
|
// Make a corresponding adjustment to the parse offset.
|
||||||
|
IASTFileLocation adjustedLocation = adjusted.getNames()[0].getFileLocation();
|
||||||
|
fAdjustedParseOffset = adjustedLocation.getNodeOffset() +
|
||||||
|
adjustedLocation.getNodeLength();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} catch (CoreException e) {
|
} catch (CoreException e) {
|
||||||
CUIPlugin.log(e);
|
CUIPlugin.log(e);
|
||||||
}
|
}
|
||||||
|
@ -125,11 +263,11 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final Lazy<Boolean> afterOpeningParenthesis = new Lazy<Boolean>() {
|
private final Lazy<Boolean> afterOpeningParenthesisOrBrace = new Lazy<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
protected Boolean calculateValue() {
|
protected Boolean calculateValue() {
|
||||||
final int invocationOffset = getInvocationOffset();
|
final int invocationOffset = getInvocationOffset();
|
||||||
final int parseOffset = getParseOffset();
|
final int parseOffset = fAdjustedParseOffset;
|
||||||
final int bound = Math.max(-1, parseOffset - 1);
|
final int bound = Math.max(-1, parseOffset - 1);
|
||||||
final IDocument document = getDocument();
|
final IDocument document = getDocument();
|
||||||
final CHeuristicScanner scanner = new CHeuristicScanner(document);
|
final CHeuristicScanner scanner = new CHeuristicScanner(document);
|
||||||
|
@ -140,12 +278,19 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
// character being searched."
|
// character being searched."
|
||||||
// If we are completing in between two empty parentheses with no space between them,
|
// If we are completing in between two empty parentheses with no space between them,
|
||||||
// this condition won't be satisfied, so we start the search one character earlier.
|
// this condition won't be satisfied, so we start the search one character earlier.
|
||||||
if (document.getChar(start) == ')')
|
if (document.getChar(start) == ')' || document.getChar(start) == '}')
|
||||||
start -= 1;
|
start -= 1;
|
||||||
} catch (BadLocationException e) {
|
} catch (BadLocationException e) {
|
||||||
}
|
}
|
||||||
final int parenthesisOffset = scanner.findOpeningPeer(start, bound, '(', ')');
|
final int parenthesisOffset = scanner.findOpeningPeer(start, bound, '(', ')');
|
||||||
return parenthesisOffset != CHeuristicScanner.NOT_FOUND;
|
if (parenthesisOffset != CHeuristicScanner.NOT_FOUND) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final int braceOffset = scanner.findOpeningPeer(start, bound, '{', '}');
|
||||||
|
if (braceOffset != CHeuristicScanner.NOT_FOUND) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -239,6 +384,7 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
Assert.isNotNull(editor);
|
Assert.isNotNull(editor);
|
||||||
fEditor= editor;
|
fEditor= editor;
|
||||||
fIsCompletion= isCompletion;
|
fIsCompletion= isCompletion;
|
||||||
|
fIsContextInformationStyle= !isCompletion;
|
||||||
fIsAutoActivated= isAutoActivated;
|
fIsAutoActivated= isAutoActivated;
|
||||||
fTU = new Lazy<ITranslationUnit>() {
|
fTU = new Lazy<ITranslationUnit>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -263,6 +409,7 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
};
|
};
|
||||||
fEditor= null;
|
fEditor= null;
|
||||||
fIsCompletion= isCompletion;
|
fIsCompletion= isCompletion;
|
||||||
|
fIsContextInformationStyle= !isCompletion;
|
||||||
fIsAutoActivated= false;
|
fIsAutoActivated= false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,6 +467,11 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
assertNotDisposed();
|
assertNotDisposed();
|
||||||
return fParseOffset.value();
|
return fParseOffset.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAdjustedParseOffset() {
|
||||||
|
assertNotDisposed();
|
||||||
|
return fAdjustedParseOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the offset where context information (parameter hints) starts.
|
* @return the offset where context information (parameter hints) starts.
|
||||||
|
@ -427,7 +579,7 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
@Override
|
@Override
|
||||||
public boolean isContextInformationStyle() {
|
public boolean isContextInformationStyle() {
|
||||||
assertNotDisposed();
|
assertNotDisposed();
|
||||||
return !fIsCompletion || (getParseOffset() != getInvocationOffset());
|
return fIsContextInformationStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAutoActivated() {
|
public boolean isAutoActivated() {
|
||||||
|
@ -444,9 +596,9 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAfterOpeningParenthesis() {
|
public boolean isAfterOpeningParenthesisOrBrace() {
|
||||||
assertNotDisposed();
|
assertNotDisposed();
|
||||||
return afterOpeningParenthesis.value();
|
return afterOpeningParenthesisOrBrace.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAfterOpeningAngleBracket() {
|
public boolean isAfterOpeningAngleBracket() {
|
||||||
|
|
|
@ -477,7 +477,7 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer
|
||||||
|
|
||||||
private void handleClass(ICPPClassType classType, IASTCompletionContext astContext,
|
private void handleClass(ICPPClassType classType, IASTCompletionContext astContext,
|
||||||
CContentAssistInvocationContext cContext, int baseRelevance, List<ICompletionProposal> proposals) {
|
CContentAssistInvocationContext cContext, int baseRelevance, List<ICompletionProposal> proposals) {
|
||||||
if (cContext.isContextInformationStyle() && cContext.isAfterOpeningParenthesis()) {
|
if (cContext.isContextInformationStyle() && cContext.isAfterOpeningParenthesisOrBrace()) {
|
||||||
addProposalsForConstructors(classType, astContext, cContext, baseRelevance, proposals);
|
addProposalsForConstructors(classType, astContext, cContext, baseRelevance, proposals);
|
||||||
} else if (classType instanceof ICPPClassTemplate) {
|
} else if (classType instanceof ICPPClassTemplate) {
|
||||||
addProposalForClassTemplate((ICPPClassTemplate) classType, cContext, baseRelevance, proposals);
|
addProposalForClassTemplate((ICPPClassTemplate) classType, cContext, baseRelevance, proposals);
|
||||||
|
@ -759,10 +759,11 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer
|
||||||
// Invocation offset and parse offset are the same if content assist is invoked while in the function
|
// Invocation offset and parse offset are the same if content assist is invoked while in the function
|
||||||
// name (i.e. before the '('). After that, the parse offset will indicate the end of the name part.
|
// name (i.e. before the '('). After that, the parse offset will indicate the end of the name part.
|
||||||
// If there is no difference between them, then we're still inside the function name part.
|
// If there is no difference between them, then we're still inside the function name part.
|
||||||
int relativeOffset = context.getInvocationOffset() - context.getParseOffset();
|
int parseOffset = context.getAdjustedParseOffset();
|
||||||
|
int relativeOffset = context.getInvocationOffset() - parseOffset;
|
||||||
if (relativeOffset == 0)
|
if (relativeOffset == 0)
|
||||||
return true;
|
return true;
|
||||||
int startOffset = context.getParseOffset();
|
int startOffset = parseOffset;
|
||||||
String completePrefix = context.getDocument().get().substring(startOffset,
|
String completePrefix = context.getDocument().get().substring(startOffset,
|
||||||
context.getInvocationOffset());
|
context.getInvocationOffset());
|
||||||
int lastChar = getLastNonWhitespaceChar(completePrefix);
|
int lastChar = getLastNonWhitespaceChar(completePrefix);
|
||||||
|
|
Loading…
Add table
Reference in a new issue