diff --git a/source/compiler-core/slang-json-native.cpp b/source/compiler-core/slang-json-native.cpp index 1f9a4dbc67e..930aa611fcc 100644 --- a/source/compiler-core/slang-json-native.cpp +++ b/source/compiler-core/slang-json-native.cpp @@ -205,6 +205,22 @@ SlangResult JSONToNativeConverter::convert(const JSONValue& in, const RttiInfo* *(UnownedStringSlice*)out = m_container->getString(in); return SLANG_OK; } + case RttiInfo::Kind::Optional: + { + if (in.getKind() == JSONValue::Kind::Null) + { + return SLANG_OK; + } + typedef List Type; + const OptionalRttiInfo* optionalRttiInfo = + static_cast(rttiInfo); + auto hasValue = (uint8_t*)out; + *hasValue = 1; + return convert( + in, + optionalRttiInfo->m_elementType, + (uint8_t*)out + optionalRttiInfo->m_valueOffset); + } case RttiInfo::Kind::List: { if (in.getKind() == JSONValue::Kind::Null) @@ -441,6 +457,24 @@ SlangResult NativeToJSONConverter::convert(const RttiInfo* rttiInfo, const void* { return SLANG_E_NOT_IMPLEMENTED; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = + static_cast(rttiInfo); + auto hasValue = (const uint8_t*)in; + if (*hasValue) + { + return convert( + optionalRttiInfo->m_elementType, + (const uint8_t*)in + optionalRttiInfo->m_valueOffset, + out); + } + else + { + out = JSONValue::makeNull(); + return SLANG_OK; + } + } case RttiInfo::Kind::List: { const ListRttiInfo* listRttiInfo = static_cast(rttiInfo); diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h index d9b17f1d524..ae0123d3071 100644 --- a/source/compiler-core/slang-json-value.h +++ b/source/compiler-core/slang-json-value.h @@ -8,6 +8,8 @@ #include "slang-json-parser.h" #include "slang-source-loc.h" +#include + namespace Slang { @@ -439,6 +441,39 @@ class JSONContainer : public RefObject List m_objectValues; }; +template +class JSONOptional +{ +public: + bool hasValue = false; + T value; + JSONOptional() = default; + JSONOptional(std::nullopt_t) {} + JSONOptional(const T& inValue) + : hasValue(true), value(inValue) + { + } +}; + +template +struct GetRttiInfo> +{ + static const OptionalRttiInfo _make() + { + OptionalRttiInfo info; + info.init>(RttiInfo::Kind::Optional); + info.m_elementType = GetRttiInfo::get(); + info.m_valueOffset = (uint32_t)offsetof(JSONOptional, value); + return info; + } + static const RttiInfo* get() + { + static const OptionalRttiInfo g_info = _make(); + return &g_info; + } +}; + + class JSONBuilder : public JSONListener { public: diff --git a/source/compiler-core/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp index 9a382e756dd..2530120d3fe 100644 --- a/source/compiler-core/slang-language-server-protocol.cpp +++ b/source/compiler-core/slang-language-server-protocol.cpp @@ -522,6 +522,7 @@ static const StructRttiInfo _makeCompletionItemRtti() builder.addField("label", &obj.label, StructRttiInfo::Flag::Optional); builder.addField("detail", &obj.detail, StructRttiInfo::Flag::Optional); builder.addField("kind", &obj.kind, StructRttiInfo::Flag::Optional); + builder.addField("sortText", &obj.sortText, StructRttiInfo::Flag::Optional); builder.addField("documentation", &obj.documentation, StructRttiInfo::Flag::Optional); builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional); builder.addField("commitCharacters", &obj.commitCharacters, StructRttiInfo::Flag::Optional); diff --git a/source/compiler-core/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h index d96099da691..ff933e8677d 100644 --- a/source/compiler-core/slang-language-server-protocol.h +++ b/source/compiler-core/slang-language-server-protocol.h @@ -610,6 +610,13 @@ struct CompletionItem */ String detail; + /** + * A string that should be used when comparing this item + * with other items. When omitted the label is used + * as the sort text for this item. + */ + JSONOptional sortText; + /** * A human-readable string that represents a doc-comment. */ diff --git a/source/core/slang-rtti-info.h b/source/core/slang-rtti-info.h index 01c042511b7..58e557dd01c 100644 --- a/source/core/slang-rtti-info.h +++ b/source/core/slang-rtti-info.h @@ -181,7 +181,7 @@ struct RttiInfo Enum, List, Dictionary, - + Optional, CountOf, }; @@ -296,6 +296,12 @@ SLANG_FORCE_INLINE StructRttiInfo::Flags combine( return StructRttiInfo::Flags(defaultValue) | flags; } +struct OptionalRttiInfo : public RttiInfo +{ + const RttiInfo* m_elementType; + uint32_t m_valueOffset; +}; + struct ListRttiInfo : public RttiInfo { const RttiInfo* m_elementType; diff --git a/source/core/slang-rtti-util.cpp b/source/core/slang-rtti-util.cpp index 92f45a3bf49..01571cab176 100644 --- a/source/core/slang-rtti-util.cpp +++ b/source/core/slang-rtti-util.cpp @@ -336,6 +336,7 @@ RttiTypeFuncs RttiUtil::getDefaultTypeFuncs(const RttiInfo* rttiInfo) case RttiInfo::Kind::List: return ListFuncs::getFuncs(); case RttiInfo::Kind::Struct: + case RttiInfo::Kind::Optional: return StructArrayFuncs::getFuncs(); default: break; @@ -556,6 +557,10 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) const OtherRttiInfo* otherRttiInfo = static_cast(rttiInfo); return otherRttiInfo->m_isDefaultFunc && otherRttiInfo->m_isDefaultFunc(rttiInfo, src); } + case RttiInfo::Kind::Optional: + { + return *(const bool*)src == (_getIntDefaultValue(defaultValue) != 0); + } default: { return false; @@ -660,6 +665,11 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) return true; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = static_cast(type); + return canMemCpy(optionalRttiInfo->m_elementType); + } default: { return type->isBuiltIn(); @@ -723,6 +733,11 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) return true; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = static_cast(type); + return canZeroInit(optionalRttiInfo->m_elementType); + } default: { return type->isBuiltIn(); @@ -783,6 +798,11 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) } while (structRttiInfo); return false; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = static_cast(type); + return hasDtor(optionalRttiInfo->m_elementType); + } default: { return !type->isBuiltIn(); @@ -894,6 +914,19 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) return; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = + static_cast(rttiInfo); + ctorArray(typeMap, GetRttiInfo::get(), dst, stride, count); + ctorArray( + typeMap, + optionalRttiInfo->m_elementType, + dst + optionalRttiInfo->m_valueOffset, + stride, + count); + return; + } } SLANG_ASSERT(!"Unexpected"); @@ -1005,6 +1038,20 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) return; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = + static_cast(rttiInfo); + copyArray(typeMap, GetRttiInfo::get(), dst, src, stride, count); + copyArray( + typeMap, + optionalRttiInfo->m_elementType, + dst + optionalRttiInfo->m_valueOffset, + src + optionalRttiInfo->m_valueOffset, + stride, + count); + return; + } } SLANG_ASSERT(!"Unexpected"); @@ -1091,6 +1138,19 @@ static bool _isStructDefault(const StructRttiInfo* type, const void* src) return; } + case RttiInfo::Kind::Optional: + { + const OptionalRttiInfo* optionalRttiInfo = + static_cast(rttiInfo); + dtorArray(typeMap, GetRttiInfo::get(), dst, stride, count); + dtorArray( + typeMap, + optionalRttiInfo->m_elementType, + dst + optionalRttiInfo->m_valueOffset, + stride, + count); + return; + } } SLANG_ASSERT(!"Unexpected"); diff --git a/source/slang/slang-ast-decl.cpp b/source/slang/slang-ast-decl.cpp index 5152f090184..f37ebef48a5 100644 --- a/source/slang/slang-ast-decl.cpp +++ b/source/slang/slang-ast-decl.cpp @@ -42,7 +42,8 @@ bool isInterfaceRequirement(Decl* decl) { if (as(ancestor)) return true; - + if (as(ancestor)) + return false; if (as(ancestor)) return false; } diff --git a/source/slang/slang-ast-print.cpp b/source/slang/slang-ast-print.cpp index 7b239c88cf7..94864b8159d 100644 --- a/source/slang/slang-ast-print.cpp +++ b/source/slang/slang-ast-print.cpp @@ -1313,17 +1313,15 @@ void ASTPrinter::addGenericParams(const DeclRef& genericDeclRef) sb << ", "; first = false; - { - ScopePart scopePart(this, Part::Type::GenericParamValue); - sb << getText(genericValParam.getName()); - } - - sb << ":"; - { ScopePart scopePart(this, Part::Type::GenericParamValueType); addType(getType(m_astBuilder, genericValParam)); } + sb << " "; + { + ScopePart scopePart(this, Part::Type::GenericParamValue); + sb << getText(genericValParam.getName()); + } } else if (auto genericTypePackParam = paramDeclRef.as()) { diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 23458c94afb..f0cd32e74f2 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -353,6 +353,8 @@ struct SemanticsDeclHeaderVisitor : public SemanticsDeclVisitorBase, void checkDifferentiableCallableCommon(CallableDecl* decl); + void checkInterfaceRequirement(Decl* decl); + void checkCallableDeclCommon(CallableDecl* decl); void visitFuncDecl(FuncDecl* funcDecl); @@ -7800,11 +7802,84 @@ void SemanticsVisitor::checkAggTypeConformance(AggTypeDecl* decl) innerMember, Diagnostics::overrideModifierNotOverridingBaseDecl, innerMember); + + if (getShared()->isInLanguageServer() && + member->getName() == getShared()->getSession()->getCompletionRequestTokenName()) + { + // If we encountered a completion request for a decl name where an 'override' + // keyword is specified, we should suggest all the base decls that can be + // overridden, but have not been overridden yet. + // + calcOverridableCompletionCandidates(type, decl, member); + } } } } } + +void SemanticsVisitor::calcOverridableCompletionCandidates( + Type* aggType, + ContainerDecl* aggTypeDecl, + Decl* memberDecl) +{ + // We are in language server and the user requested to list + // all base interface methods that can be overrided in a conforming type. + // Collect all base interfaces methods that hasn't been overridden and + // suggest them as completion candidates. + if (as(aggTypeDecl)) + { + // If the aggType is an interface, we don't have any base methods to suggest. + return; + } + auto inheritanceInfo = getShared()->getInheritanceInfo(aggType); + HashSet overridenDecls; + for (auto member : aggTypeDecl->getMembers()) + { + member = maybeGetInner(member); + if (!as(member)) + continue; + if (auto overridedDeclModifier = member->findModifier()) + { + overridenDecls.add(overridedDeclModifier->overridedDecl); + } + } + auto& contentAssistInfo = getShared()->getLinkage()->contentAssistInfo; + contentAssistInfo.completionSuggestions.scopeKind = CompletionSuggestions::ScopeKind::Decl; + auto varDeclBase = as(memberDecl); + contentAssistInfo.completionSuggestions.formatMode = + varDeclBase ? CompletionSuggestions::FormatMode::FuncSignatureWithoutReturnType + : CompletionSuggestions::FormatMode::FullSignature; + + List candidateItems; + for (auto facet : inheritanceInfo.facets) + { + // Extensions don't contribute overridable members. + if (facet->kind == Facet::Kind::Extension) + continue; + + auto interfaceDecl = facet->getDeclRef().as(); + if (!interfaceDecl) + continue; + for (auto requirement : interfaceDecl.getDecl()->getMembers()) + { + requirement = maybeGetInner(requirement); + if (!as(requirement)) + continue; + if (!overridenDecls.contains(requirement)) + { + auto requirementDeclRef = DeclRef(requirement); + requirementDeclRef = + createDefaultSubstitutionsIfNeeded(m_astBuilder, this, requirementDeclRef); + candidateItems.add(LookupResultItem(requirementDeclRef)); + } + } + } + // Insert overridable candidates at the front of the list, so they are preferred over + // the completion results from checking ordinary type exprs. + contentAssistInfo.completionSuggestions.candidateItems.insertRange(0, candidateItems); +} + void SemanticsDeclBasesVisitor::_validateCrossModuleInheritance( AggTypeDeclBase* decl, InheritanceDecl* inheritanceDecl) @@ -10209,6 +10284,26 @@ void SemanticsDeclHeaderVisitor::checkDifferentiableCallableCommon(CallableDecl* } } +void SemanticsDeclHeaderVisitor::checkInterfaceRequirement(Decl* decl) +{ + if (isInterfaceRequirement(decl)) + { + if (auto funcBase = as(decl)) + { + if (!as(decl) && funcBase->body != nullptr) + { + getSink()->diagnose(decl, Diagnostics::nonMethodInterfaceRequirementCannotHaveBody); + return; + } + } + // Interface requirement cannot be `override`. + if (decl->hasModifier()) + { + getSink()->diagnose(decl, Diagnostics::interfaceRequirementCannotBeOverride); + } + } +} + void SemanticsDeclHeaderVisitor::checkCallableDeclCommon(CallableDecl* decl) { for (auto paramDecl : decl->getParameters()) @@ -10238,6 +10333,7 @@ void SemanticsDeclHeaderVisitor::checkCallableDeclCommon(CallableDecl* decl) } } + checkInterfaceRequirement(decl); checkVisibility(decl); } @@ -10642,7 +10738,10 @@ void SemanticsDeclHeaderVisitor::visitAbstractStorageDeclCommon(ContainerDecl* d // bool anyAccessors = decl->getMembersOfType().isNonEmpty(); - + for (auto accessor : decl->getMembersOfType()) + { + checkInterfaceRequirement(accessor); + } if (!anyAccessors) { GetterDecl* getterDecl = m_astBuilder->create(); diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 365f21b1c8b..d4d431914f0 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -2100,6 +2100,11 @@ struct SemanticsVisitor : public SemanticsContext void checkExtensionConformance(ExtensionDecl* decl); + void calcOverridableCompletionCandidates( + Type* aggType, + ContainerDecl* aggTypeDecl, + Decl* memberDecl); + void checkAggTypeConformance(AggTypeDecl* decl); bool isIntegerBaseType(BaseType baseType); diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index 2a4707db566..d135744afef 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -2149,6 +2149,9 @@ void SemanticsVisitor::checkModifiers(ModifiableSyntaxNode* syntaxNode) // an error if the modifier is not allowed on the declaration. if (as(modifier)) ignoreUnallowedModifier = true; + else if ( + getLinkage()->contentAssistInfo.checkingMode == ContentAssistCheckingMode::Completion) + ignoreUnallowedModifier = true; // may return a list of modifiers auto checkedModifier = checkModifier(modifier, syntaxNode, ignoreUnallowedModifier); diff --git a/source/slang/slang-content-assist-info.h b/source/slang/slang-content-assist-info.h index 6d4503cc542..77102bf66e6 100644 --- a/source/slang/slang-content-assist-info.h +++ b/source/slang/slang-content-assist-info.h @@ -22,7 +22,17 @@ struct CompletionSuggestions HLSLSemantics, Capabilities }; + + enum class FormatMode + { + Name, + FullSignature, + FuncSignatureWithoutReturnType + }; + ScopeKind scopeKind = ScopeKind::Invalid; + FormatMode formatMode = FormatMode::Name; + List candidateItems; Type* swizzleBaseType = nullptr; IntegerLiteralValue elementCount[2] = {0, 0}; diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 64b47ece8f2..d54e1a3e0fe 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -1507,6 +1507,17 @@ DIAGNOSTIC( "requirement in the form of a simple value must be declared as 'static const'.") DIAGNOSTIC(30310, Error, typeIsNotDifferentiable, "type '$0' is not differentiable.") +DIAGNOSTIC( + 30311, + Error, + nonMethodInterfaceRequirementCannotHaveBody, + "non-method interface requirement cannot have a body.") +DIAGNOSTIC( + 30312, + Error, + interfaceRequirementCannotBeOverride, + "interface requirement cannot override a base declaration.") + // Interop DIAGNOSTIC( 30400, diff --git a/source/slang/slang-language-server-completion.cpp b/source/slang/slang-language-server-completion.cpp index 240102a22e2..59aa3e4d81b 100644 --- a/source/slang/slang-language-server-completion.cpp +++ b/source/slang/slang-language-server-completion.cpp @@ -5,6 +5,7 @@ #include "../core/slang-char-util.h" #include "../core/slang-file-system.h" #include "slang-ast-all.h" +#include "slang-ast-print.h" #include "slang-check-impl.h" #include "slang-language-server-ast-lookup.h" #include "slang-language-server.h" @@ -22,7 +23,7 @@ static const char* kDeclKeywords[] = { "protected", "typedef", "typealias", "uniform", "export", "groupshared", "extension", "associatedtype", "namespace", "This", "using", "__generic", "__exported", "import", "enum", "cbuffer", "tbuffer", "func", - "functype", "typename", "each", "expand", "where"}; + "functype", "typename", "each", "expand", "where", "override"}; static const char* kStmtKeywords[] = { "if", "else", @@ -96,6 +97,8 @@ static const char* hlslSemanticNames[] = { "packoffset", "read", "write", + "SV_BaseInstanceID", + "SV_BaryCentrics", "SV_ClipDistance", "SV_CullDistance", "SV_Coverage", @@ -126,6 +129,8 @@ static const char* hlslSemanticNames[] = { "SV_VertexID", "SV_ViewID", "SV_ViewportArrayIndex", + "SV_VulkanVertexID", + "SV_VulkanInstanceID", "SV_ShadingRate", "SV_StartVertexLocation", "SV_StartInstanceLocation", @@ -511,10 +516,72 @@ LanguageServerResult CompletionContext::tryCompleteMemberAndSy return collectMembersAndSymbols(); } +String CompletionContext::formatDeclForCompletion( + DeclRef declRef, + ASTBuilder* astBuilder, + CompletionSuggestions::FormatMode formatMode, + int& outNameStart) +{ + outNameStart = 0; + switch (formatMode) + { + case CompletionSuggestions::FormatMode::Name: + return getText(declRef.getDecl()->getName()); + default: + break; + } + + ASTPrinter printer(astBuilder, ASTPrinter::OptionFlag::ParamNames); + if (auto genDecl = as(declRef)) + declRef = astBuilder->getMemberDeclRef(genDecl, genDecl.getDecl()->inner); + auto callableDecl = as(declRef); + if (!callableDecl) + return String(); + if (formatMode == CompletionSuggestions::FormatMode::FullSignature) + { + printer.addType(callableDecl.getDecl()->returnType.type); + printer.getStringBuilder() << " "; + } + outNameStart = (int)printer.getStringBuilder().getLength(); + printer.getStringBuilder() << getText(declRef.getDecl()->getName()); + auto outerGeneric = as(declRef.getParent()); + if (outerGeneric) + { + printer.addGenericParams(outerGeneric); + } + printer.addDeclParams(declRef); + if (callableDecl.getDecl()->errorType.type != astBuilder->getBottomType() && + callableDecl.getDecl()->errorType.type != astBuilder->getErrorType()) + { + printer.getStringBuilder() << " throws "; + printer.addType(callableDecl.getDecl()->errorType); + } + if (outerGeneric) + { + for (auto constraint : + outerGeneric.getDecl()->getMembersOfType()) + { + printer.getStringBuilder() << "\n"; + bool indentUsingTab = indent.startsWith("\t"); + if (indentUsingTab) + printer.getStringBuilder() << "\t"; + else + printer.getStringBuilder() << " "; + printer.getStringBuilder() << "where "; + printer.addType(constraint->sub.type); + if (constraint->isEqualityConstraint) + printer.getStringBuilder() << " == "; + else + printer.getStringBuilder() << " : "; + printer.addType(constraint->sup.type); + } + } + return printer.getString(); +} + CompletionResult CompletionContext::collectMembersAndSymbols() { List result; - auto linkage = version->linkage; if (linkage->contentAssistInfo.completionSuggestions.scopeKind == CompletionSuggestions::ScopeKind::Swizzle) @@ -563,7 +630,14 @@ CompletionResult CompletionContext::collectMembersAndSymbols() if (!member->getName()) continue; LanguageServerProtocol::CompletionItem item; - item.label = member->getName()->text; + int nameStart = 0; + item.label = formatDeclForCompletion( + suggestedItem.declRef, + linkage->m_astBuilder, + linkage->contentAssistInfo.completionSuggestions.formatMode, + nameStart); + if (item.label.getLength() == 0) + continue; item.kind = LanguageServerProtocol::kCompletionItemKindKeyword; if (as(member)) { @@ -627,7 +701,27 @@ CompletionResult CompletionContext::collectMembersAndSymbols() item.kind = LanguageServerProtocol::kCompletionItemKindClass; } item.data = String(i); + if (linkage->contentAssistInfo.completionSuggestions.formatMode != + CompletionSuggestions::FormatMode::Name) + { + item.sortText = + (StringBuilder() << i << ":" << getText(member->getName())).produceString(); + } + result.add(item); + if (nameStart > 1) + { + // If the completion item is for a full function signature, add the return type part too + // as a separate item. + item.label = item.label.getUnownedSlice().head(nameStart - 1); + item.kind = LanguageServerProtocol::kCompletionItemKindStruct; + item.sortText = + (StringBuilder() + << linkage->contentAssistInfo.completionSuggestions.candidateItems.getCount() + << ":" << item.label) + .produceString(); + result.add(item); + } } if (addKeywords) { diff --git a/source/slang/slang-language-server-completion.h b/source/slang/slang-language-server-completion.h index fab1c8a7559..513538ac8a9 100644 --- a/source/slang/slang-language-server-completion.h +++ b/source/slang/slang-language-server-completion.h @@ -42,7 +42,10 @@ struct CompletionContext CommitCharacterBehavior commitCharacterBehavior; Int line; Int col; + String indent; + // The token range of original request, in 0-based UTF16 code units. + LanguageServerProtocol::Range requestRange; LanguageServerResult tryCompleteMemberAndSymbol(); LanguageServerResult tryCompleteHLSLSemantic(); LanguageServerResult tryCompleteAttributes(); @@ -55,6 +58,11 @@ struct CompletionContext CompletionResult collectMembersAndSymbols(); + String formatDeclForCompletion( + DeclRef decl, + ASTBuilder* astBuilder, + CompletionSuggestions::FormatMode formatMode, + int& outNameStart); void createSwizzleCandidates( List& result, Type* type, diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp index cd9818f65d7..6daeb75ad5d 100644 --- a/source/slang/slang-language-server.cpp +++ b/source/slang/slang-language-server.cpp @@ -1157,15 +1157,17 @@ LanguageServerResult LanguageServerCore::completion( } // Ajust cursor position to the beginning of the current/last identifier. + Index firstTokenCharOffset = cursorOffset; cursorOffset--; while (cursorOffset > 0 && _isIdentifierChar(doc->getText()[cursorOffset])) { + firstTokenCharOffset = cursorOffset; cursorOffset--; } // Never show suggestions when the user is typing a number. - if (cursorOffset + 1 >= 0 && cursorOffset + 1 < doc->getText().getLength() && - CharUtil::isDigit(doc->getText()[cursorOffset + 1])) + if (firstTokenCharOffset >= 0 && firstTokenCharOffset < doc->getText().getLength() && + CharUtil::isDigit(doc->getText()[firstTokenCharOffset])) { return std::nullopt; } @@ -1180,12 +1182,32 @@ LanguageServerResult LanguageServerCore::completion( version->linkage->contentAssistInfo.cursorCol = utf8Col; Slang::CompletionContext context; context.server = this; - context.cursorOffset = cursorOffset; + context.cursorOffset = firstTokenCharOffset; context.version = version; context.doc = doc.Ptr(); context.canonicalPath = canonicalPath.getUnownedSlice(); context.line = utf8Line; context.col = utf8Col; + Int firstNonWhiteSpace = 0; + auto lineContent = doc->getLine(utf8Line); + while (firstNonWhiteSpace < lineContent.getLength() && + CharUtil::isWhitespace(lineContent[firstNonWhiteSpace])) + firstNonWhiteSpace++; + context.indent = doc->getLine(utf8Line).head(firstNonWhiteSpace); + doc->offsetToLineCol(firstTokenCharOffset, utf8Line, utf8Col); + doc->oneBasedUTF8LocToZeroBasedUTF16Loc( + utf8Line, + utf8Col, + context.requestRange.start.line, + context.requestRange.start.character); + + auto utf8TokenLen = doc->getTokenLength(firstTokenCharOffset); + doc->oneBasedUTF8LocToZeroBasedUTF16Loc( + utf8Line, + utf8Col + utf8TokenLen, + context.requestRange.end.line, + context.requestRange.end.character); + context.commitCharacterBehavior = m_commitCharacterBehavior; if (args.context.triggerKind == kCompletionTriggerKindTriggerCharacter && (args.context.triggerCharacter == " " || args.context.triggerCharacter == "[" || diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 9727140f36d..a019f97c4ad 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -1443,8 +1443,7 @@ static NameLoc ParseDeclName(Parser* parser) } else { - nameToken = parser->ReadToken(TokenType::Identifier); - return NameLoc(nameToken); + return expectIdentifier(parser); } } @@ -2037,6 +2036,7 @@ static RefPtr parseDirectAbstractDeclarator( switch (parser->tokenReader.peekTokenType()) { case TokenType::Identifier: + case TokenType::CompletionRequest: { auto nameDeclarator = new NameDeclarator(); nameDeclarator->flavor = Declarator::Flavor::name; @@ -5048,6 +5048,20 @@ static DeclBase* ParseDeclWithModifiers( decl->loc = loc; } break; + case TokenType::CompletionRequest: + { + if (modifiers.hasModifier()) + { + auto resultDecl = parser->astBuilder->create(); + resultDecl->nameAndLoc = expectIdentifier(parser); + decl = resultDecl; + } + else + { + decl = ParseDeclaratorDecl(parser, containerDecl, modifiers); + } + } + break; // If nothing else matched, we try to parse an "ordinary" declarator-based declaration default: decl = ParseDeclaratorDecl(parser, containerDecl, modifiers); diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h index 1a12a2b42a9..71f5af5c6d3 100644 --- a/source/slang/slang-workspace-version.h +++ b/source/slang/slang-workspace-version.h @@ -102,7 +102,7 @@ class DocumentVersion : public RefObject col = Index(offset - getLineStart(lines[line - 1])) + 1; } if (line > 0 && line <= lines.getCount()) - col = UTF8Util::calcCodePointCount(lines[line - 1].head(col)); + col = UTF8Util::calcCodePointCount(lines[line - 1].head(col - 1)) + 1; } // Get line from 1-based index. diff --git a/tests/diagnostics/interfaces/interface-override.slang b/tests/diagnostics/interfaces/interface-override.slang new file mode 100644 index 00000000000..12539895b19 --- /dev/null +++ b/tests/diagnostics/interfaces/interface-override.slang @@ -0,0 +1,28 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): + +interface IBase +{ + int f(); +} + +interface IDerived : IBase +{ + //CHECK-DAG: ([[# @LINE+1]]): error 30311: + __init() {} + + property int value{ + get; + + //CHECK-DAG: ([[# @LINE+1]]): error 30311: + set{ } + } + + __subscript(int index)->int + { + //CHECK-DAG: ([[# @LINE+1]]): error 30311: + get{} + } + + //CHECK-DAG: ([[# @LINE+1]]): error 30312: + override int f() {} +} \ No newline at end of file diff --git a/tests/language-server/override-completion.slang b/tests/language-server/override-completion.slang new file mode 100644 index 00000000000..7cfd52b3e90 --- /dev/null +++ b/tests/language-server/override-completion.slang @@ -0,0 +1,16 @@ +//TEST:LANG_SERVER(filecheck=CHECK): +interface IBar{} +interface IFoo +{ + int eval(); + void execute(int x); +} + +struct Impl : IFoo +{ +//COMPLETE:12,14 + override +} + +//CHECK-DAG: int eval +//CHECK-DAG: void execute \ No newline at end of file