diff --git a/dsymbol/src/dsymbol/conversion/first.d b/dsymbol/src/dsymbol/conversion/first.d index 6cad7ff8..9055d480 100644 --- a/dsymbol/src/dsymbol/conversion/first.d +++ b/dsymbol/src/dsymbol/conversion/first.d @@ -1175,7 +1175,17 @@ private: else if (t2.builtinType !is tok!"") lookup.breadcrumbs.insert(getBuiltinTypeName(t2.builtinType)); else if (t2.typeIdentifierPart !is null) - writeIotcTo(t2.typeIdentifierPart, lookup.breadcrumbs); + { + // Check if this is a template instantiation + if (t2.typeIdentifierPart.identifierOrTemplateInstance.templateInstance !is null) + { + writeIotcToForLookup(t2.typeIdentifierPart, *lookup); + } + else + { + writeIotcTo(t2.typeIdentifierPart, lookup.breadcrumbs); + } + } // TODO: support __vector, traits and mixin foreach (suffix; type.typeSuffixes) @@ -1354,6 +1364,161 @@ bool isDitto(scope const(char)[] comment) return comment.length == 5 && icmp(comment, "ditto") == 0; } +void writeIotcTo(L)(const TypeIdentifierPart tip, ref TypeLookup lookup) nothrow +{ + if (!tip.identifierOrTemplateInstance) + return; + + if (tip.dot) + lookup.breadcrumbs.insert(MODULE_SYMBOL_NAME); + + if (tip.identifierOrTemplateInstance.identifier != tok!"") + { + lookup.breadcrumbs.insert(internString(tip.identifierOrTemplateInstance.identifier.text)); + } + else + { + lookup.breadcrumbs.insert(internString(tip.identifierOrTemplateInstance.templateInstance.identifier.text)); + // Extract template arguments + auto ta = tip.identifierOrTemplateInstance.templateInstance.templateArguments; + if (ta !is null && ta.templateSingleArgument !is null) + { + import dparse.ast : TemplateSingleArgument; + const TemplateSingleArgument tsArg = ta.templateSingleArgument; + if (tsArg.type !is null) + { + // Add the type argument to templateArguments + writeTypeToBreadcrumbs(tsArg.type, lookup.templateArguments); + } + else if (tsArg.token != tok!"") + { + lookup.templateArguments.insert(internString(tsArg.token.text)); + } + // Mark as template instantiation + lookup.kind = TypeLookupKind.templateInstantiation; + } + else if (ta !is null && ta.templateArgumentList !is null) + { + import dparse.ast : TemplateArgumentList, TemplateArgument; + const TemplateArgumentList taList = ta.templateArgumentList; + if (taList.items !is null) + { + foreach (const taItem; taList.items) + { + if (taItem.type !is null) + { + writeTypeToBreadcrumbs(taItem.type, lookup.templateArguments); + } + else if (taItem.assignExpression !is null) + { + // For expressions, just use the identifier text if available + lookup.templateArguments.insert(internString(taItem.assignExpression.toString())); + } + else if (taItem.typeofExpression !is null) + { + lookup.templateArguments.insert(TYPEOF_SYMBOL_NAME); + } + } + // Mark as template instantiation + lookup.kind = TypeLookupKind.templateInstantiation; + } + } + else if (ta !is null && ta.namedTemplateArgumentList !is null) + { + import dparse.ast : NamedTemplateArgumentList, NamedTemplateArgument; + const NamedTemplateArgumentList ntList = ta.namedTemplateArgumentList; + if (ntList.items !is null) + { + foreach (const ntItem; ntList.items) + { + if (ntItem.type !is null) + { + writeTypeToBreadcrumbs(ntItem.type, lookup.templateArguments); + } + else if (ntItem.assignExpression !is null) + { + lookup.templateArguments.insert(internString(ntItem.assignExpression.toString())); + } + } + // Mark as template instantiation + lookup.kind = TypeLookupKind.templateInstantiation; + } + } + } + + // the indexer of a TypeIdentifierPart means either that there's + // a static array dimension or that a type is selected in a type list. + // we can only handle the first case since dsymbol does not process templates yet. + if (tip.indexer) + lookup.breadcrumbs.insert(ARRAY_SYMBOL_NAME); + + if (tip.typeIdentifierPart) + writeIotcTo(tip.typeIdentifierPart, lookup); +} + +private void writeTypeToBreadcrumbsImpl(T)(ref const(Type) type, ref T output) nothrow +{ + auto t2 = type.type2; + if (t2.typeofExpression !is null) + { + output.insert(TYPEOF_SYMBOL_NAME); + } + else if (t2.superOrThis == tok!"this") + { + output.insert(internString("this")); + } + else if (t2.superOrThis == tok!"super") + { + output.insert(internString("super")); + } + + if (t2.type !is null) + { + writeTypeToBreadcrumbsImpl(t2.type, output); + } + else if (t2.typeIdentifierPart !is null) + { + writeTypeIdentifierPartToBreadcrumbs(t2.typeIdentifierPart, output); + } + else if (t2.builtinType != tok!"") + { + output.insert(getBuiltinTypeName(t2.builtinType)); + } + + foreach (suffix; type.typeSuffixes) + { + if (suffix.type) + output.insert(ASSOC_ARRAY_SYMBOL_NAME); + else if (suffix.array) + output.insert(ARRAY_SYMBOL_NAME); + else if (suffix.star != tok!"") + output.insert(POINTER_SYMBOL_NAME); + else if (suffix.delegateOrFunction != tok!"") + { + output.insert(FUNCTION_SYMBOL_NAME); + } + } +} + +private void writeTypeIdentifierPartToBreadcrumbs(T)(const TypeIdentifierPart tip, ref T output) nothrow +{ + if (tip.identifierOrTemplateInstance.identifier != tok!"") + { + output.insert(internString(tip.identifierOrTemplateInstance.identifier.text)); + } + else + { + output.insert(internString(tip.identifierOrTemplateInstance.templateInstance.identifier.text)); + } + if (tip.typeIdentifierPart) + writeTypeIdentifierPartToBreadcrumbs(tip.typeIdentifierPart, output); +} + +private void writeTypeToBreadcrumbs(ref const(Type) type, ref UnrolledList!istring breadcrumbs) nothrow +{ + writeTypeToBreadcrumbsImpl(type, breadcrumbs); +} + void writeIotcTo(T)(const TypeIdentifierPart tip, ref T output) nothrow { if (!tip.identifierOrTemplateInstance) @@ -1377,6 +1542,73 @@ void writeIotcTo(T)(const TypeIdentifierPart tip, ref T output) nothrow writeIotcTo(tip.typeIdentifierPart, output); } +/// Writes a TypeIdentifierPart to a TypeLookup, handling template instantiations +void writeIotcToForLookup(const TypeIdentifierPart tip, ref TypeLookup lookup) nothrow +{ + if (!tip.identifierOrTemplateInstance) + return; + + if (tip.dot) + lookup.breadcrumbs.insert(MODULE_SYMBOL_NAME); + + if (tip.identifierOrTemplateInstance.identifier != tok!"") + { + lookup.breadcrumbs.insert(internString(tip.identifierOrTemplateInstance.identifier.text)); + } + else + { + lookup.breadcrumbs.insert(internString(tip.identifierOrTemplateInstance.templateInstance.identifier.text)); + // Extract template arguments + auto ta = tip.identifierOrTemplateInstance.templateInstance.templateArguments; + if (ta !is null && ta.templateSingleArgument !is null) + { + import dparse.ast : TemplateSingleArgument; + const TemplateSingleArgument tsArg = ta.templateSingleArgument; + // For TemplateSingleArgument, we only have a token + if (tsArg.token != tok!"") + { + lookup.templateArguments.insert(internString(tsArg.token.text)); + } + // Mark as template instantiation + lookup.kind = TypeLookupKind.templateInstantiation; + } + else if (ta !is null && ta.namedTemplateArgumentList !is null) + { + import dparse.ast : NamedTemplateArgumentList, NamedTemplateArgument; + const NamedTemplateArgumentList ntList = ta.namedTemplateArgumentList; + if (ntList.items !is null) + { + foreach (const ntItem; ntList.items) + { + if (ntItem.type !is null) + { + writeTypeToBreadcrumbs(ntItem.type, lookup.templateArguments); + } + else if (ntItem.assignExpression !is null) + { + // For the name of the argument (not the value) + if (ntItem.name != tok!"") + { + lookup.templateArguments.insert(internString(ntItem.name.text)); + } + } + } + // Mark as template instantiation + lookup.kind = TypeLookupKind.templateInstantiation; + } + } + } + + // the indexer of a TypeIdentifierPart means either that there's + // a static array dimension or that a type is selected in a type list. + // we can only handle the first case since dsymbol does not process templates yet. + if (tip.indexer) + lookup.breadcrumbs.insert(ARRAY_SYMBOL_NAME); + + if (tip.typeIdentifierPart) + writeIotcToForLookup(tip.typeIdentifierPart, lookup); +} + auto byIdentifier(const IdentifierOrTemplateChain iotc) nothrow { import std.algorithm : map; diff --git a/dsymbol/src/dsymbol/conversion/second.d b/dsymbol/src/dsymbol/conversion/second.d index bac4b503..3739ff20 100644 --- a/dsymbol/src/dsymbol/conversion/second.d +++ b/dsymbol/src/dsymbol/conversion/second.d @@ -545,11 +545,143 @@ void resolveType(DSymbol* symbol, ref TypeLookups typeLookups, // issue 94 else if (lookup.kind == TypeLookupKind.inherit) resolveInheritance(symbol, typeLookups, moduleScope, cache); + else if (lookup.kind == TypeLookupKind.templateInstantiation) + resolveTemplateInstantiation(symbol, lookup, moduleScope, cache); else assert(false, "How did this happen?"); } } + +void resolveTemplateInstantiation(DSymbol* symbol, TypeLookup* lookup, + Scope* moduleScope, ref ModuleCache cache) +{ + if (moduleScope is null) + return; + + // Get the template name from breadcrumbs + if (lookup.breadcrumbs.empty) + return; + + istring templateName = lookup.breadcrumbs.back; + DSymbol*[] templateSymbols = moduleScope.getSymbolsByNameAndCursor(templateName, symbol.location); + if (templateSymbols.length == 0) + { + symbol.typeSymbolName = templateName; + return; + } + + DSymbol* templateSymbol = templateSymbols[0]; + + // Ensure we found a template + if (templateSymbol.kind != CompletionKind.templateName + && templateSymbol.kind != CompletionKind.structName + && templateSymbol.kind != CompletionKind.className + && templateSymbol.kind != CompletionKind.unionName + && templateSymbol.qualifier != SymbolQualifier.templated) + { + symbol.type = templateSymbol; + symbol.ownType = false; + return; + } + + // Create a new DSymbol for the instantiated type + // This symbol will have its template parameters resolved + auto instantiatedType = GCAllocator.instance.make!DSymbol( + templateSymbol.name, + templateSymbol.kind == CompletionKind.templateName + ? CompletionKind.structName : templateSymbol.kind); + instantiatedType.location = symbol.location; + instantiatedType.symbolFile = templateSymbol.symbolFile; + instantiatedType.qualifier = templateSymbol.qualifier; + + // Get template parameter names from the template declaration + import std.array : Appender; + auto paramNamesAppender = Appender!(istring[])(); + foreach (child; templateSymbol.opSlice()) + { + if (child.kind == CompletionKind.typeTmpParam + || child.kind == CompletionKind.aliasName) + { + paramNamesAppender.put(child.name); + } + } + istring[] templateParamNames = paramNamesAppender.data; + + // Map template parameters to arguments + // Create a substitution map for resolving member types + DSymbol*[istring] substitutionMap; + size_t argIndex = 0; + foreach (argName; lookup.templateArguments[]) + { + if (argIndex < templateParamNames.length) + { + // Try to resolve the argument type + DSymbol* argType = moduleScope.getFirstSymbolByNameAndCursor(argName, symbol.location); + if (argType is null) + { + // Create a fallback symbol + argType = GCAllocator.instance.make!DSymbol(argName, CompletionKind.structName); + argType.symbolFile = templateSymbol.symbolFile; + } + substitutionMap[templateParamNames[argIndex]] = argType; + } + argIndex++; + } + + // Copy children from template, resolving template parameter types + foreach (child; templateSymbol.opSlice()) + { + if (child.kind == CompletionKind.typeTmpParam + || child.kind == CompletionKind.aliasName) + { + // Skip template parameters themselves, but add their resolved types as members + if (child.name in substitutionMap) + { + auto resolvedChild = GCAllocator.instance.make!DSymbol( + substitutionMap[child.name].name, + CompletionKind.typeTmpParam, + substitutionMap[child.name].type ? substitutionMap[child.name].type : substitutionMap[child.name]); + resolvedChild.symbolFile = child.symbolFile; + resolvedChild.ownType = false; + instantiatedType.addChild(resolvedChild, true); + } + } + else if (child.type !is null && child.type.name in substitutionMap) + { + // This member's type is a template parameter - resolve it + auto resolvedMember = GCAllocator.instance.make!DSymbol( + child.name, + child.kind, + substitutionMap[child.type.name]); + resolvedMember.symbolFile = child.symbolFile; + resolvedMember.callTip = child.callTip; + resolvedMember.doc = child.doc; + resolvedMember.protection = child.protection; + resolvedMember.qualifier = child.qualifier; + instantiatedType.addChild(resolvedMember, true); + } + else + { + // Copy other children as-is + auto copiedChild = GCAllocator.instance.make!DSymbol( + child.name, + child.kind, + child.type); + copiedChild.symbolFile = child.symbolFile; + copiedChild.callTip = child.callTip; + copiedChild.doc = child.doc; + copiedChild.protection = child.protection; + copiedChild.qualifier = child.qualifier; + copiedChild.ownType = false; + instantiatedType.addChild(copiedChild, true); + } + } + + symbol.type = instantiatedType; + symbol.ownType = true; +} + void typeSwap(ref DSymbol* currentSymbol) { while (currentSymbol !is null && currentSymbol.type !is currentSymbol diff --git a/dsymbol/src/dsymbol/type_lookup.d b/dsymbol/src/dsymbol/type_lookup.d index 8f69cebb..df4c2e5c 100644 --- a/dsymbol/src/dsymbol/type_lookup.d +++ b/dsymbol/src/dsymbol/type_lookup.d @@ -13,6 +13,7 @@ enum TypeLookupKind : ubyte mixinTemplate, varOrFunType, selectiveImport, + templateInstantiation, } /** @@ -36,4 +37,6 @@ struct TypeLookup UnrolledList!istring breadcrumbs; /// The kind of type lookup TypeLookupKind kind; + /// For template instantiations, stores the template arguments (in order) + UnrolledList!istring templateArguments; } diff --git a/tests/tc_templates/actual.txt b/tests/tc_templates/actual.txt new file mode 100644 index 00000000..4662fdcf --- /dev/null +++ b/tests/tc_templates/actual.txt @@ -0,0 +1,9 @@ +identifiers +Vec2 h +alignof k +init k +mangleof k +sizeof k +stringof k +tupleof k +value v diff --git a/tests/tc_templates/expected.txt b/tests/tc_templates/expected.txt new file mode 100644 index 00000000..4662fdcf --- /dev/null +++ b/tests/tc_templates/expected.txt @@ -0,0 +1,9 @@ +identifiers +Vec2 h +alignof k +init k +mangleof k +sizeof k +stringof k +tupleof k +value v diff --git a/tests/tc_templates/file.d b/tests/tc_templates/file.d new file mode 100644 index 00000000..50d34a32 --- /dev/null +++ b/tests/tc_templates/file.d @@ -0,0 +1,12 @@ +struct Data(T) { + T value; +} + + +struct Vec2{} + +void main() { + Data!Vec2 mydata; + + mydata.; +} diff --git a/tests/tc_templates/run.sh b/tests/tc_templates/run.sh new file mode 100644 index 00000000..3c551a64 --- /dev/null +++ b/tests/tc_templates/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -c 97 > actual.txt +diff actual.txt expected.txt --strip-trailing-cr