diff options
Diffstat (limited to 'source/slang/slang-language-server-completion.cpp')
| -rw-r--r-- | source/slang/slang-language-server-completion.cpp | 229 |
1 files changed, 224 insertions, 5 deletions
diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp index b5864d972..e1758ca5d 100644 --- a/source/slang/slang-language-server-completion.cpp +++ b/source/slang/slang-language-server-completion.cpp @@ -580,6 +580,217 @@ String CompletionContext::formatDeclForCompletion( return printer.getString(); } +// Returns true if `exprNode` is the same as `targetExpr`, or if the original expr node +// of `exprNode` before any checking/transformation is the same as `targetExpr`. +bool matchExpr(Expr* exprNode, SyntaxNode* targetExpr) +{ + if (!exprNode) + return false; + if (exprNode == targetExpr) + return true; + if (auto invokeExpr = as<AppExprBase>(exprNode)) + return matchExpr(invokeExpr->originalFunctionExpr, targetExpr); + if (auto overloadedExpr = as<OverloadedExpr>(exprNode)) + return matchExpr(overloadedExpr->originalExpr, targetExpr); + if (auto partiallyAppliedExpr = as<PartiallyAppliedGenericExpr>(exprNode)) + return matchExpr(partiallyAppliedExpr->originalExpr, targetExpr); + if (auto extractExistentialExpr = as<ExtractExistentialValueExpr>(exprNode)) + return matchExpr(extractExistentialExpr->originalExpr, targetExpr); + if (auto declRefExpr = as<DeclRefExpr>(exprNode)) + return matchExpr(declRefExpr->originalExpr, targetExpr); + return false; +} + +// Infer the accepted types at the completion position based on the AST nodes. +// +List<Type*> CompletionContext::getExpectedTypesAtCompletion(const List<ASTLookupResult>& astNodes) +{ + List<Type*> expectedType; + if (astNodes.getCount() == 0) + return expectedType; + auto& path = astNodes.getFirst().path; + if (path.getCount() < 2) + return expectedType; + auto completionExprNode = path.getLast(); + auto parentNode = path[path.getCount() - 2]; + auto collectArgumentType = [&](AppExprBase* appExpr, Index argIndex) + { + if (!appExpr) + return; + auto functionExpr = appExpr->functionExpr; + if (!functionExpr) + return; + if (as<InvokeExpr>(appExpr)) + { + // If we are in an invoke expr, we will use the parameter type of the + // callee as the expected type. + auto processDeclRefCallee = [&](DeclRef<Decl> calleeDeclRef) + { + auto decl = calleeDeclRef.getDecl(); + auto callableDecl = as<CallableDecl>(decl); + if (!callableDecl) + return; + Index paramIndex = 0; + for (auto paramDeclRef : + getMembersOfType<ParamDecl>(version->linkage->getASTBuilder(), callableDecl)) + { + if (paramIndex == argIndex) + { + expectedType.add(getType(version->linkage->getASTBuilder(), paramDeclRef)); + return; + } + paramIndex++; + } + }; + if (auto declRefExpr = as<DeclRefExpr>(functionExpr)) + processDeclRefCallee(declRefExpr->declRef); + else if (auto overloadedExpr = as<OverloadedExpr>(functionExpr)) + { + for (auto& lookupResult : overloadedExpr->lookupResult2) + processDeclRefCallee(lookupResult.declRef); + } + } + else if (as<GenericAppExpr>(appExpr)) + { + auto declRefExpr = as<DeclRefExpr>(functionExpr); + if (!declRefExpr) + return; + auto genericDecl = as<GenericDecl>(declRefExpr->declRef.getDecl()); + if (!genericDecl) + return; + + for (auto member : genericDecl->getMembers()) + { + if (auto valParamDecl = as<GenericValueParamDecl>(member)) + { + if (valParamDecl->parameterIndex == argIndex) + { + expectedType.add(valParamDecl->type.type); + return; + } + } + } + } + }; + + if (auto implicitCastExpr = as<ImplicitCastExpr>(parentNode)) + { + // If the completion request is in (SomeType)(!completionRequest), then we should prefer any + // candidates that has `SomeType`. + if (implicitCastExpr->arguments.getCount() == 1 && + matchExpr(implicitCastExpr->arguments[0], completionExprNode)) + { + if (as<DeclRefType>(implicitCastExpr->type.type)) + expectedType.add(implicitCastExpr->type.type); + } + return expectedType; + } + if (auto invokeExpr = as<AppExprBase>(parentNode)) + { + // If parent node is an invoke expr, check if we are in an argument position. + for (Index i = 0; i < invokeExpr->arguments.getCount(); i++) + { + if (matchExpr(invokeExpr->arguments[i], completionExprNode)) + { + // If we are in an argument position, we will use the expected type of the + // argument. + collectArgumentType(invokeExpr, i); + break; + } + } + return expectedType; + } + if (auto varDecl = as<VarDeclBase>(parentNode)) + { + if (!varDecl) + return expectedType; + if (!matchExpr(varDecl->initExpr, completionExprNode)) + return expectedType; + if (as<DeclRefType>(varDecl->type.type)) + { + expectedType.add(varDecl->type.type); + } + return expectedType; + } + return expectedType; +} + +Index CompletionContext::determineCompletionItemSortOrder( + Decl* item, + const List<Type*>& expectedTypes) +{ + if (expectedTypes.getCount() == 0) + return -1; + + // Test if `itemType` matches `expectedType`, and return the relevance of the match. + // -1 means no match, a positive number means a match. + // The smaller the number, the more relevant the match is, and the item will be listed + // earlier in the completion list. + auto matchType = [&](Type* itemType, DeclRefType* expectedType) -> Index + { + if (itemType == expectedType) + return 1; // Exact match + + auto declRef = isDeclRefTypeOf<Decl>(itemType); + if (!declRef) + return -1; // No match + + if (declRef.getDecl() == expectedType->getDeclRef().getDecl()) + return 2; // Match by decl + + // We may also want to extend the matching logic to include subtyping or other + // coercion relationships. But for now, we will just check for simple matches + // to avoid performance problems. + // + return -1; + }; + + Index result = -1; + + // If we have any expected types, we will sort the completion candiate items by their relevance + // to the expected types. + // If the item has expected type, we will assign a sort order to make it appear at the top + // of the completion list. + for (auto et : expectedTypes) + { + Index currentSortOrder = -1; + auto etDeclRefType = as<DeclRefType>(et); + if (!etDeclRefType) + continue; + if (item == etDeclRefType->getDeclRef().getDecl()) + { + if (as<EnumDecl>(item)) + currentSortOrder = 0; + else if (!as<InterfaceDecl>(item)) + currentSortOrder = 1; + } + else if (auto varItem = as<VarDeclBase>(item)) + { + currentSortOrder = matchType(varItem->type.type, etDeclRefType); + } + else if (auto callableItem = as<CallableDecl>(item)) + { + // If the item is a callable decl, we will check if the return type matches the expected + // type. + currentSortOrder = matchType(callableItem->returnType.type, etDeclRefType); + } + if (result == -1 || (currentSortOrder != -1 && currentSortOrder < result)) + { + // If we have a better match, we will update the result. + result = currentSortOrder; + } + } + // Always list decls within the same module first. + // Note if result == 0, it means the item is representing the expected enum type itself, + // so we always want to list it first by not increasing `result`. + if (result > 0 && getModule(item) != parsedModule) + result++; + // List core module decls last. + if (result > 0 && isFromCoreModule(item)) + result++; + return result; +} + CompletionResult CompletionContext::collectMembersAndSymbols() { List<LanguageServerProtocol::CompletionItem> result; @@ -626,7 +837,15 @@ CompletionResult CompletionContext::collectMembersAndSymbols() addKeywords = false; break; } - + auto lookupResults = findASTNodesAt( + doc, + version->linkage->getSourceManager(), + parsedModule->getModuleDecl(), + ASTLookupType::CompletionRequest, + canonicalPath, + line, + col); + auto expectedTypes = getExpectedTypesAtCompletion(lookupResults); HashSet<String> deduplicateSet; for (Index i = 0; i < linkage->contentAssistInfo.completionSuggestions.candidateItems.getCount(); @@ -734,13 +953,13 @@ CompletionResult CompletionContext::collectMembersAndSymbols() item.kind = LanguageServerProtocol::kCompletionItemKindClass; } item.data = String(i); - if (linkage->contentAssistInfo.completionSuggestions.formatMode != - CompletionSuggestions::FormatMode::Name) + + Index sortOrder = determineCompletionItemSortOrder(member, expectedTypes); + if (sortOrder != -1) { item.sortText = - (StringBuilder() << i << ":" << getText(member->getName())).produceString(); + (StringBuilder() << sortOrder << ":" << getText(member->getName())).produceString(); } - result.add(item); if (nameStart > 1) { |
