1
0
Fork 0
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:
Nathan Ridge 2017-01-20 11:11:25 -05:00 committed by Gerrit Code Review @ Eclipse.org
parent a395647e48
commit 1c60b844c5
3 changed files with 204 additions and 11 deletions

View file

@ -1227,6 +1227,46 @@ public class CompletionTests extends CompletionTestBase {
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 {
// Waldo(int, int);
// };

View file

@ -24,12 +24,25 @@ import org.eclipse.jface.text.ITextViewer;
import org.eclipse.ui.IEditorPart;
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.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.index.IIndex;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.model.ICProject;
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.PreferenceConstants;
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
@ -54,6 +67,12 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
private final boolean fIsCompletion;
private final boolean fIsAutoActivated;
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>() {
@Override
@ -67,6 +86,19 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
private final Lazy<Integer> fParseOffset = new Lazy<Integer>() {
@Override
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) {
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>() {
@Override
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;
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) {
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
protected Boolean calculateValue() {
final int invocationOffset = getInvocationOffset();
final int parseOffset = getParseOffset();
final int parseOffset = fAdjustedParseOffset;
final int bound = Math.max(-1, parseOffset - 1);
final IDocument document = getDocument();
final CHeuristicScanner scanner = new CHeuristicScanner(document);
@ -140,12 +278,19 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
// character being searched."
// 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.
if (document.getChar(start) == ')')
if (document.getChar(start) == ')' || document.getChar(start) == '}')
start -= 1;
} catch (BadLocationException e) {
}
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);
fEditor= editor;
fIsCompletion= isCompletion;
fIsContextInformationStyle= !isCompletion;
fIsAutoActivated= isAutoActivated;
fTU = new Lazy<ITranslationUnit>() {
@Override
@ -263,6 +409,7 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
};
fEditor= null;
fIsCompletion= isCompletion;
fIsContextInformationStyle= !isCompletion;
fIsAutoActivated= false;
}
@ -320,6 +467,11 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
assertNotDisposed();
return fParseOffset.value();
}
public int getAdjustedParseOffset() {
assertNotDisposed();
return fAdjustedParseOffset;
}
/**
* @return the offset where context information (parameter hints) starts.
@ -427,7 +579,7 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
@Override
public boolean isContextInformationStyle() {
assertNotDisposed();
return !fIsCompletion || (getParseOffset() != getInvocationOffset());
return fIsContextInformationStyle;
}
public boolean isAutoActivated() {
@ -444,9 +596,9 @@ public class CContentAssistInvocationContext extends ContentAssistInvocationCont
super.dispose();
}
public boolean isAfterOpeningParenthesis() {
public boolean isAfterOpeningParenthesisOrBrace() {
assertNotDisposed();
return afterOpeningParenthesis.value();
return afterOpeningParenthesisOrBrace.value();
}
public boolean isAfterOpeningAngleBracket() {

View file

@ -477,7 +477,7 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer
private void handleClass(ICPPClassType classType, IASTCompletionContext astContext,
CContentAssistInvocationContext cContext, int baseRelevance, List<ICompletionProposal> proposals) {
if (cContext.isContextInformationStyle() && cContext.isAfterOpeningParenthesis()) {
if (cContext.isContextInformationStyle() && cContext.isAfterOpeningParenthesisOrBrace()) {
addProposalsForConstructors(classType, astContext, cContext, baseRelevance, proposals);
} else if (classType instanceof ICPPClassTemplate) {
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
// 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.
int relativeOffset = context.getInvocationOffset() - context.getParseOffset();
int parseOffset = context.getAdjustedParseOffset();
int relativeOffset = context.getInvocationOffset() - parseOffset;
if (relativeOffset == 0)
return true;
int startOffset = context.getParseOffset();
int startOffset = parseOffset;
String completePrefix = context.getDocument().get().substring(startOffset,
context.getInvocationOffset());
int lastChar = getLastNonWhitespaceChar(completePrefix);