Skip to content

Start moving toward a callback model for code-completion type-checking #4605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions include/swift/Sema/IDETypeChecking.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,18 @@ namespace swift {
/// \returns True on applied, false on not applied.
bool isExtensionApplied(DeclContext &DC, Type Ty, const ExtensionDecl *ED);

/// The kind of type checking to perform for code completion.
enum class CompletionTypeCheckKind {
/// Type check the expression as normal.
Normal,

/// Type check the argument to an Objective-C #keyPath.
ObjCKeyPath,
};

/// \brief Return the type of an expression parsed during code completion, or
/// None on error.
Optional<Type> getTypeOfCompletionContextExpr(
ASTContext &Ctx,
DeclContext *DC,
CompletionTypeCheckKind kind,
Expr *&parsedExpr,
ConcreteDeclRef &referencedDecl);

/// \brief Return the type of an ObjCKeyPath expression parsed during code
/// completion, or None on error.
Optional<Type> getTypeOfObjCKeyPath(DeclContext *DC, ObjCKeyPathExpr *E);

/// Typecheck the sequence expression \p parsedExpr for code completion.
///
/// This requires that \p parsedExpr is a SequenceExpr and that it contains:
Expand All @@ -121,11 +115,29 @@ namespace swift {
/// \returns true on success, false on error.
bool typeCheckTopLevelCodeDecl(TopLevelCodeDecl *TLCD);

/// An interface between code completion and the expression type-checker.
class CodeCompletionTypeCheckingCallbacks {
public:
virtual ~CodeCompletionTypeCheckingCallbacks();
/// Whether we're intereseted in resolving the given expression.
///
/// \param opaque whether to treat \p E as opaque for constraint generation
/// and just introduce a free type variable.
virtual bool isInterestingExpr(Expr *E, bool &opaque) = 0;

/// Report the type and optionally the corresponding declaration of an
/// interesting expression.
virtual void resolvedInterestingExpr(Expr *E, Type T,
ConcreteDeclRef D) = 0;
};

/// A unique_ptr for LazyResolver that can perform additional cleanup.
using OwnedResolver = std::unique_ptr<LazyResolver, void(*)(LazyResolver*)>;

/// Creates a lazy type resolver for use in lookups.
OwnedResolver createLazyResolver(ASTContext &Ctx);
OwnedResolver createLazyResolver(
ASTContext &Ctx,
CodeCompletionTypeCheckingCallbacks *ccCallbacks = nullptr);
}

#endif
117 changes: 86 additions & 31 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,15 +1238,46 @@ void CodeCompletionContext::sortCompletionResults(
[](const ResultAndName &entry) { return entry.result; });
}

namespace {
class CodeCompletionTypechecking : public CodeCompletionTypeCheckingCallbacks {
llvm::SmallDenseMap<Expr *, SmallVector<std::pair<Type, ConcreteDeclRef>, 2>>
typeMap;
typedef llvm::PointerIntPair<Expr *, 1, bool> ExprAndUseFreeTypeVar;
llvm::SmallVector<ExprAndUseFreeTypeVar, 3> interestingExprs;

bool isInterestingExpr(Expr *E, bool &overrrideCSGen) override {
for (auto exprAndUseFreeTypeVar : interestingExprs) {
if (exprAndUseFreeTypeVar.getPointer() == E) {
overrrideCSGen = exprAndUseFreeTypeVar.getInt();
return true;
}
}
return false;
}
void resolvedInterestingExpr(Expr *E, Type T, ConcreteDeclRef D) override {
typeMap[E].push_back(std::make_pair(T, D));
}

public:
void addInterestingExpr(Expr *E, bool useFreeTypeVar) {
interestingExprs.emplace_back(E, useFreeTypeVar);
}
ArrayRef<std::pair<Type, ConcreteDeclRef>> getViableTypes(Expr *E) {
return typeMap[E];
}
};
} // end anonymous namespace

namespace {
class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
CodeCompletionContext &CompletionContext;
CodeCompletionTypechecking CCTypeChecking;
std::vector<RequestedCachedModule> RequestedModules;
CodeCompletionConsumer &Consumer;
CodeCompletionExpr *CodeCompleteTokenExpr = nullptr;
AssignExpr *AssignmentExpr;
CallExpr *FuncCallExpr;
UnresolvedMemberExpr *UnresolvedExpr;
AssignExpr *AssignmentExpr = nullptr;
CallExpr *FuncCallExpr = nullptr;
UnresolvedMemberExpr *UnresolvedExpr = nullptr;
bool UnresolvedExprInReturn;
std::vector<std::string> TokensBeforeUnresolvedExpr;
CompletionKind Kind = CompletionKind::None;
Expand Down Expand Up @@ -1352,33 +1383,41 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
Optional<std::pair<Type, ConcreteDeclRef>> typeCheckParsedExpr() {
assert(ParsedExpr && "should have an expression");

// Figure out the kind of type-check we'll be performing.
auto CheckKind = CompletionTypeCheckKind::Normal;
if (Kind == CompletionKind::KeyPathExpr ||
Kind == CompletionKind::KeyPathExprDot)
CheckKind = CompletionTypeCheckKind::ObjCKeyPath;
Kind == CompletionKind::KeyPathExprDot) {
auto keyPath = cast<ObjCKeyPathExpr>(ParsedExpr);
if (auto T = getTypeOfObjCKeyPath(CurDeclContext, keyPath))
return std::make_pair(*T, ConcreteDeclRef());
return None;
}

auto typeAndDecls = CCTypeChecking.getViableTypes(ParsedExpr);
if (!typeAndDecls.empty())
return typeAndDecls[0];

// If we've already successfully type-checked the expression for some
// reason, just return the type.
// FIXME: if it's ErrorType but we've already typechecked we shouldn't
// typecheck again. rdar://21466394
if (CheckKind == CompletionTypeCheckKind::Normal &&
ParsedExpr->getType() && !ParsedExpr->getType()->is<ErrorType>())
if (ParsedExpr->getType() && !ParsedExpr->getType()->is<ErrorType>())
return std::make_pair(ParsedExpr->getType(),
ParsedExpr->getReferencedDecl());

eraseErrorTypes(ParsedExpr);
if (ParsedExpr->getType())
eraseErrorTypes(ParsedExpr);

ConcreteDeclRef ReferencedDecl = nullptr;
Expr *ModifiedExpr = ParsedExpr;
if (auto T = getTypeOfCompletionContextExpr(P.Context, CurDeclContext,
CheckKind, ModifiedExpr,
ReferencedDecl)) {
auto T = getTypeOfCompletionContextExpr(P.Context, CurDeclContext,
ModifiedExpr, ReferencedDecl);
typeAndDecls = CCTypeChecking.getViableTypes(ParsedExpr);
if (!typeAndDecls.empty()) {
return typeAndDecls[0];
} else if (T) {
// FIXME: even though we don't apply the solution, the type checker may
// modify the original expression. We should understand what effect that
// may have on code completion.
ParsedExpr = ModifiedExpr;

return std::make_pair(*T, ReferencedDecl);
}
return None;
Expand Down Expand Up @@ -1776,13 +1815,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
Optional<RequestedResultsTy> RequestedCachedResults;

public:
CompletionLookup(CodeCompletionResultSink &Sink,
ASTContext &Ctx,
CompletionLookup(CodeCompletionResultSink &Sink, ASTContext &Ctx,
OwnedResolver &&TypeResolver,
const DeclContext *CurrDeclContext)
: Sink(Sink), Ctx(Ctx),
TypeResolver(createLazyResolver(Ctx)), CurrDeclContext(CurrDeclContext),
Importer(static_cast<ClangImporter *>(CurrDeclContext->getASTContext().
getClangModuleLoader())) {
: Sink(Sink), Ctx(Ctx), TypeResolver(std::move(TypeResolver)),
CurrDeclContext(CurrDeclContext),
Importer(static_cast<ClangImporter *>(
CurrDeclContext->getASTContext().getClangModuleLoader())) {

// Determine if we are doing code completion inside a static method.
if (CurrDeclContext) {
Expand Down Expand Up @@ -3271,7 +3310,6 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
if (auto T = getTypeOfCompletionContextExpr(
CurrDeclContext->getASTContext(),
const_cast<DeclContext *>(CurrDeclContext),
CompletionTypeCheckKind::Normal,
tempExpr,
referencedDecl))
addPostfixOperatorCompletion(op, *T);
Expand Down Expand Up @@ -3398,20 +3436,24 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
void typeCheckLeadingSequence(SmallVectorImpl<Expr *> &sequence) {
Expr *expr =
SequenceExpr::create(CurrDeclContext->getASTContext(), sequence);
auto LHS = sequence.back();
eraseErrorTypes(expr);
// Take advantage of the fact the type-checker leaves the types on the AST.
if (!typeCheckExpression(const_cast<DeclContext *>(CurrDeclContext),
expr)) {
if (auto binexpr = dyn_cast<BinaryExpr>(expr)) {
// Rebuild the sequence from the type-checked version.
sequence.clear();
flattenBinaryExpr(binexpr, sequence);
return;
typeCheckExpression(const_cast<DeclContext *>(CurrDeclContext), expr);
if (auto binexpr = dyn_cast<BinaryExpr>(expr)) {
// Rebuild the sequence from the type-checked version.
sequence.clear();
flattenBinaryExpr(binexpr, sequence);
// Check that each non-operator is typed.
for (unsigned i = 0; i < sequence.size(); i += 2) {
if (!sequence[i]->getType() || sequence[i]->getType()->is<ErrorType>())
goto fallback;
}
return;
}

fallback:
// Fall back to just using the immediate LHS.
auto LHS = sequence.back();
sequence.clear();
sequence.push_back(LHS);
}
Expand Down Expand Up @@ -5061,6 +5103,15 @@ void CodeCompletionCallbacksImpl::doneParsing() {
// Add keywords even if type checking fails completely.
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);

if (ParsedExpr)
CCTypeChecking.addInterestingExpr(ParsedExpr, false);
if (UnresolvedExpr)
CCTypeChecking.addInterestingExpr(UnresolvedExpr, true);
if (CodeCompleteTokenExpr)
CCTypeChecking.addInterestingExpr(CodeCompleteTokenExpr, true);

OwnedResolver TypeResolver = createLazyResolver(P.Context, &CCTypeChecking);

if (!typecheckContext())
return;

Expand Down Expand Up @@ -5090,7 +5141,7 @@ void CodeCompletionCallbacksImpl::doneParsing() {
return;

CompletionLookup Lookup(CompletionContext.getResultSink(), P.Context,
CurDeclContext);
std::move(TypeResolver), CurDeclContext);
if (ExprType) {
Lookup.setIsStaticMetatype(ParsedExpr->isStaticallyDerivedMetatype());
}
Expand Down Expand Up @@ -5480,7 +5531,9 @@ void swift::ide::lookupCodeCompletionResultsFromModule(
CodeCompletionResultSink &targetSink, const Module *module,
ArrayRef<std::string> accessPath, bool needLeadingDot,
const DeclContext *currDeclContext) {
CompletionLookup Lookup(targetSink, module->getASTContext(), currDeclContext);
CompletionLookup Lookup(targetSink, module->getASTContext(),
createLazyResolver(module->getASTContext()),
currDeclContext);
Lookup.getVisibleDeclsOfModule(module, accessPath, needLeadingDot);
}

Expand Down Expand Up @@ -5557,3 +5610,5 @@ void SimpleCachingCodeCompletionConsumer::handleResultsAndModules(

handleResults(context.takeResults());
}

CodeCompletionTypeCheckingCallbacks::~CodeCompletionTypeCheckingCallbacks() {}
12 changes: 9 additions & 3 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,8 +1669,7 @@ namespace {
}

Expr *visitCodeCompletionExpr(CodeCompletionExpr *expr) {
// Do nothing with code completion expressions.
return expr;
return simplifyExprType(expr);
}

Expr *handleIntegerLiteralExpr(LiteralExpr *expr) {
Expand Down Expand Up @@ -2306,7 +2305,14 @@ namespace {
// Find the selected member.
auto memberLocator = cs.getConstraintLocator(
expr, ConstraintLocator::UnresolvedMember);
auto selected = getOverloadChoice(memberLocator);
auto selectedOpt = getOverloadChoiceIfAvailable(memberLocator);
if (!selectedOpt) {
assert(tc.CodeCompletion && "missing unresolved-member overload");
expr->setType(resultTy);
return expr;
}

auto selected = *selectedOpt;
auto member = selected.choice.getDecl();

// If the member came by optional unwrapping, then unwrap the base type.
Expand Down
Loading