From 07877be7379d020fcbfbb611755cba1824328971 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Mon, 1 Jun 2026 00:02:37 +0900 Subject: [PATCH] Fix type alias target graph edges for issue #1976 --- changelog.d/unreleased/1976.fixed.md | 18 ++++++++ src/CodeIndex/Database/DbReader.cs | 4 +- .../Languages/SwiftReferenceExtractor.cs | 42 ++++++++++++++++- .../Languages/TypeScriptReferenceExtractor.cs | 30 ++++++++++++- ...eferenceExtractor.PatternTypeReferences.cs | 45 ++++++++++++------- .../ReferenceExtractor.TypeReferences.cs | 24 ++++++++++ src/CodeIndex/Models/SymbolKindCatalog.cs | 1 + .../ReferenceExtractorTests.cs | 15 +++++++ 8 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 changelog.d/unreleased/1976.fixed.md diff --git a/changelog.d/unreleased/1976.fixed.md b/changelog.d/unreleased/1976.fixed.md new file mode 100644 index 0000000000..a9d1d07e99 --- /dev/null +++ b/changelog.d/unreleased/1976.fixed.md @@ -0,0 +1,18 @@ +--- +category: fixed +issues: + - 1976 +affected: + - src/CodeIndex/Database/DbReader.cs + - src/CodeIndex/Models/SymbolKindCatalog.cs + - src/CodeIndex/Indexer/References/Languages/SwiftReferenceExtractor.cs + - src/CodeIndex/Indexer/References/Languages/TypeScriptReferenceExtractor.cs +--- + +## English + +- **Type alias targets now participate in graph traversal (#1976)** — Swift and TypeScript type alias RHS references now emit explicit alias-target edges so graph queries can continue from an alias symbol to its underlying type. + +## 日本語 + +- **型エイリアスの参照先が graph traversal に参加するようになりました (#1976)** — Swift と TypeScript の type alias RHS が明示的な alias-target edge を出すようになり、graph query が alias symbol から underlying type へ辿れるようになりました。 diff --git a/src/CodeIndex/Database/DbReader.cs b/src/CodeIndex/Database/DbReader.cs index 194723af36..884658b41d 100644 --- a/src/CodeIndex/Database/DbReader.cs +++ b/src/CodeIndex/Database/DbReader.cs @@ -171,7 +171,9 @@ ELSE 0 // Razor の `razor_event_binding`、React の `consumes_hook`、C++ の `friend` は依存関係 graph query に含める。 // C# closure の `capture` は lambda 本体から外側 local への依存であり、impact に参加する。 // C# generic invocation type arguments are included when tied to an actual call (#2062). - internal const string CallGraphReferenceKindsSql = "('augmentation', 'call', 'instantiate', 'generic_type_argument', 'subscribe', 'unsubscribe', 'razor_event_binding', 'friend', 'consumes_hook', 'capture')"; + // Type alias target edges are explicit indirection edges, allowing graph walks to continue + // from an alias symbol to its underlying type without promoting ordinary annotations (#1976). + internal const string CallGraphReferenceKindsSql = "('augmentation', 'call', 'instantiate', 'generic_type_argument', 'subscribe', 'unsubscribe', 'razor_event_binding', 'friend', 'consumes_hook', 'capture', 'type_alias_target')"; private const string SyntheticTopLevelCallerName = ""; private const string SyntheticTopLevelCallerKind = "function"; diff --git a/src/CodeIndex/Indexer/References/Languages/SwiftReferenceExtractor.cs b/src/CodeIndex/Indexer/References/Languages/SwiftReferenceExtractor.cs index 00cd553eca..e43ee03aea 100644 --- a/src/CodeIndex/Indexer/References/Languages/SwiftReferenceExtractor.cs +++ b/src/CodeIndex/Indexer/References/Languages/SwiftReferenceExtractor.cs @@ -1033,8 +1033,15 @@ private static void EmitTypealiasRhsTypeReferences( if (typeEnd <= typeStart) return; + var rhsExpression = preparedLine.Substring(typeStart, typeEnd - typeStart); + var rhsContainer = new SymbolRecord + { + Kind = "typealias", + Name = ExtractTypealiasName(preparedLine, typealiasIndex + "typealias".Length), + }; + var typeReferenceContainer = resolveContainerForColumn(typeStart); TypedLanguageReferenceExtractor.EmitTypeExpressionReferences( - preparedLine.Substring(typeStart, typeEnd - typeStart), + rhsExpression, typeStart, "swift", references, @@ -1042,7 +1049,38 @@ private static void EmitTypealiasRhsTypeReferences( fileId, context, lineNumber, - resolveContainerForColumn(typeStart)); + typeReferenceContainer); + ReferenceExtractor.EmitTypeAliasTargetExpressionReferences( + rhsExpression, + typeStart, + "swift", + references, + seen, + fileId, + context, + lineNumber, + rhsContainer); + } + + private static string ExtractTypealiasName(string line, int nameStart) + { + while (nameStart < line.Length && char.IsWhiteSpace(line[nameStart])) + nameStart++; + if (nameStart >= line.Length) + return string.Empty; + + if (line[nameStart] == '`') + { + var close = line.IndexOf('`', nameStart + 1); + if (close > nameStart) + return line.Substring(nameStart, close - nameStart + 1); + } + + var nameEnd = nameStart; + while (nameEnd < line.Length && (line[nameEnd] == '_' || char.IsLetterOrDigit(line[nameEnd]))) + nameEnd++; + + return line.Substring(nameStart, nameEnd - nameStart); } private static void EmitAssociatedTypeReferences( diff --git a/src/CodeIndex/Indexer/References/Languages/TypeScriptReferenceExtractor.cs b/src/CodeIndex/Indexer/References/Languages/TypeScriptReferenceExtractor.cs index 39ee9a42f8..7bae1dbf01 100644 --- a/src/CodeIndex/Indexer/References/Languages/TypeScriptReferenceExtractor.cs +++ b/src/CodeIndex/Indexer/References/Languages/TypeScriptReferenceExtractor.cs @@ -1267,8 +1267,15 @@ private static void EmitTypeAliasTypeReferences( if (rhsEnd <= rhsStart) return; + var rhsExpression = preparedLine.Substring(rhsStart, rhsEnd - rhsStart); + var rhsContainer = new SymbolRecord + { + Kind = "typealias", + Name = ExtractTypeAliasName(preparedLine, nameEnd), + }; + var typeReferenceContainer = resolveContainerForColumn(rhsStart); TypedLanguageReferenceExtractor.EmitTypeExpressionReferences( - preparedLine.Substring(rhsStart, rhsEnd - rhsStart), + rhsExpression, rhsStart, "typescript", references, @@ -1276,7 +1283,26 @@ private static void EmitTypeAliasTypeReferences( fileId, context, lineNumber, - resolveContainerForColumn(rhsStart)); + typeReferenceContainer); + ReferenceExtractor.EmitTypeAliasTargetExpressionReferences( + rhsExpression, + rhsStart, + "typescript", + references, + seen, + fileId, + context, + lineNumber, + rhsContainer); + } + + private static string ExtractTypeAliasName(string line, int nameEnd) + { + var nameStart = nameEnd; + while (nameStart > 0 && IsTypeScriptIdentifierPart(line[nameStart - 1])) + nameStart--; + + return line.Substring(nameStart, nameEnd - nameStart); } private static bool TryFindTypeAliasShape(string line, out int nameEnd, out int assignmentIndex) diff --git a/src/CodeIndex/Indexer/References/ReferenceExtractor.PatternTypeReferences.cs b/src/CodeIndex/Indexer/References/ReferenceExtractor.PatternTypeReferences.cs index 270fb4f331..73884128ba 100644 --- a/src/CodeIndex/Indexer/References/ReferenceExtractor.PatternTypeReferences.cs +++ b/src/CodeIndex/Indexer/References/ReferenceExtractor.PatternTypeReferences.cs @@ -1130,11 +1130,12 @@ private static void AddTypeExpressionSegmentsForLanguage( string context, int lineNumber, SymbolRecord? container, - IReadOnlySet? ignoredSegments = null) + IReadOnlySet? ignoredSegments = null, + string referenceKind = "type_reference") { if (language == "typescript") { - AddTypeScriptTypeExpressionSegments( + AddTypeScriptTypeExpressionSegmentsCore( references, seen, fileId, @@ -1167,7 +1168,8 @@ internal static void AddTypeScriptTypeExpressionSegments( int expressionStartInLine, string context, int lineNumber, - SymbolRecord? container) + SymbolRecord? container, + string referenceKind = "type_reference") { if (TypedLanguageReferenceExtractor.TryEmitTypeScriptFunctionTypeExpressionReferences( expression, @@ -1281,7 +1283,8 @@ private static int SkipTypeScriptTemplateLiteral( int expressionStartInLine, string context, int lineNumber, - SymbolRecord? container) + SymbolRecord? container, + string referenceKind = "type_reference") { int i = start + 1; while (i < text.Length) @@ -1850,11 +1853,12 @@ internal static void AddTypeExpressionSegments( int lineNumber, SymbolRecord? container, string language, - IReadOnlySet? ignoredSegments = null) + IReadOnlySet? ignoredSegments = null, + string referenceKind = "type_reference") { if (language == "typescript") { - AddTypeScriptTypeExpressionSegments( + AddTypeScriptTypeExpressionSegmentsCore( references, seen, fileId, @@ -1863,7 +1867,8 @@ internal static void AddTypeExpressionSegments( context, lineNumber, container, - ignoredSegments); + ignoredSegments, + referenceKind); return; } @@ -1906,7 +1911,7 @@ internal static void AddTypeExpressionSegments( continue; } - AddReference(references, seen, fileId, rustSegment, expressionStartInLine + rustSegmentStart, "type_reference", context, lineNumber, container); + AddReference(references, seen, fileId, rustSegment, expressionStartInLine + rustSegmentStart, referenceKind, context, lineNumber, container); i--; continue; } @@ -1934,7 +1939,8 @@ internal static void AddTypeExpressionSegments( lineNumber, container, language, - ignoredSegments: ignoredSegments); + ignoredSegments: ignoredSegments, + referenceKind: referenceKind); i = closeIndex; continue; } @@ -1956,7 +1962,8 @@ internal static void AddTypeExpressionSegments( lineNumber, container, language, - ignoredSegments: ignoredSegments); + ignoredSegments: ignoredSegments, + referenceKind: referenceKind); i = closeIndex; continue; } @@ -2006,7 +2013,7 @@ internal static void AddTypeExpressionSegments( continue; } - AddTypeReferenceSegment(references, seen, fileId, segment, expressionStartInLine + segmentStart, context, lineNumber, container, language, isEscapedCSharpIdentifier, ignoredSegments); + AddTypeReferenceSegment(references, seen, fileId, segment, expressionStartInLine + segmentStart, context, lineNumber, container, language, isEscapedCSharpIdentifier, ignoredSegments, referenceKind); i--; } } @@ -2046,7 +2053,7 @@ private static bool IsSwiftMetatypeSuffixSegment(string expression, int segmentS return previous >= 0 && expression[previous] == '.'; } - private static void AddTypeScriptTypeExpressionSegments( + private static void AddTypeScriptTypeExpressionSegmentsCore( List references, HashSet seen, long fileId, @@ -2055,7 +2062,8 @@ private static void AddTypeScriptTypeExpressionSegments( string context, int lineNumber, SymbolRecord? container, - IReadOnlySet? ignoredSegments = null) + IReadOnlySet? ignoredSegments = null, + string referenceKind = "type_reference") { ignoredSegments ??= TypeScriptTypeExpressionIgnoredSegments; @@ -2081,7 +2089,8 @@ private static void AddTypeScriptTypeExpressionSegments( seen, fileId, container, - ignoredSegments); + ignoredSegments, + referenceKind); continue; } @@ -2115,7 +2124,8 @@ private static void AddTypeScriptTypeExpressionSegments( lineNumber, container, "typescript", - ignoredSegments: ignoredSegments); + ignoredSegments: ignoredSegments, + referenceKind: referenceKind); } } @@ -2140,7 +2150,8 @@ private static int ScanTypeScriptTemplateLiteralForTypeExpression( HashSet seen, long fileId, SymbolRecord? container, - IReadOnlySet? ignoredSegments) + IReadOnlySet? ignoredSegments, + string referenceKind = "type_reference") { int i = startIndex + 1; while (i < expression.Length) @@ -2165,7 +2176,7 @@ private static int ScanTypeScriptTemplateLiteralForTypeExpression( if (holeEnd < 0) return expression.Length; - AddTypeScriptTypeExpressionSegments( + AddTypeScriptTypeExpressionSegmentsCore( references, seen, fileId, diff --git a/src/CodeIndex/Indexer/References/ReferenceExtractor.TypeReferences.cs b/src/CodeIndex/Indexer/References/ReferenceExtractor.TypeReferences.cs index 817ac6df39..fa421195ca 100644 --- a/src/CodeIndex/Indexer/References/ReferenceExtractor.TypeReferences.cs +++ b/src/CodeIndex/Indexer/References/ReferenceExtractor.TypeReferences.cs @@ -591,6 +591,30 @@ internal static void AddTypeReferenceSegment( }); } + internal static void EmitTypeAliasTargetExpressionReferences( + string expression, + int expressionStartInLine, + string language, + List references, + HashSet seen, + long fileId, + string context, + int lineNumber, + SymbolRecord? container) + { + AddTypeExpressionSegments( + references, + seen, + fileId, + expression, + expressionStartInLine, + context, + lineNumber, + container, + language, + referenceKind: "type_alias_target"); + } + private static SymbolRecord? FindInnermostContainer(IReadOnlyList candidates, int lineNumber) { foreach (var candidate in candidates) diff --git a/src/CodeIndex/Models/SymbolKindCatalog.cs b/src/CodeIndex/Models/SymbolKindCatalog.cs index a1bde5ea47..e9e88dab23 100644 --- a/src/CodeIndex/Models/SymbolKindCatalog.cs +++ b/src/CodeIndex/Models/SymbolKindCatalog.cs @@ -89,6 +89,7 @@ public static class SymbolKindCatalog "stage", "razor_event_binding", "subscribe", + "type_alias_target", "type_reference", "unsubscribe", "use", diff --git a/tests/CodeIndex.Tests/ReferenceExtractorTests.cs b/tests/CodeIndex.Tests/ReferenceExtractorTests.cs index 823d520ab3..0bbfab88e8 100644 --- a/tests/CodeIndex.Tests/ReferenceExtractorTests.cs +++ b/tests/CodeIndex.Tests/ReferenceExtractorTests.cs @@ -5752,6 +5752,11 @@ public void Extract_TypeScriptTypeAliasGenericDefaults_EmitsDefaultAndRhsTypeRef reference.SymbolName == "Record" && reference.ReferenceKind == "type_reference" && reference.Context == "type Dict = Record;"); + Assert.Contains(references, reference => + reference.SymbolName == "Record" + && reference.ReferenceKind == "type_alias_target" + && reference.ContainerKind == "typealias" + && reference.ContainerName == "Dict"); } [Fact] @@ -26180,6 +26185,16 @@ struct Failure {} Assert.Contains(references, reference => reference.SymbolName == "Failure" && reference.ReferenceKind == "type_reference"); + Assert.Contains(references, reference => + reference.SymbolName == "Request" + && reference.ReferenceKind == "type_alias_target" + && reference.ContainerKind == "typealias" + && reference.ContainerName == "Loader"); + Assert.Contains(references, reference => + reference.SymbolName == "Result" + && reference.ReferenceKind == "type_alias_target" + && reference.ContainerKind == "typealias" + && reference.ContainerName == "LoadResult"); } [Fact]