feat(domain): add structured type reference system (CodeTypeRef)#44
feat(domain): add structured type reference system (CodeTypeRef)#44
Conversation
Implements Issue #41 Part 4 - structured type system for cross-language type analysis. All existing tests pass (backward compatible). New CodeTypeRef class supports: - Generic/template types with recursive arguments - Union/intersection types (TypeScript) - Pointer/reference types (C/C++/Go/Rust) - Go-specific: slice, map, channel - Rust-specific: trait bounds, lifetimes - Function types, tuple types, nullable types Updated domain classes with optional TypeRef fields: - CodeField.TypeRef - CodeProperty.TypeRef - CodeFunction.ReturnTypeRef
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughIntroduces a new cross-language type model Changes
Sequence Diagram(s)sequenceDiagram
participant Parser as "Language Parser"
participant Listener as "AST Listener"
participant Builder as "TypeRefBuilder"
participant Domain as "CodeField/CodeProperty/CodeFunction"
participant Tests as "Serializer / Tests"
rect rgba(52,152,219,0.5)
Parser->>Listener: emit parse context (type / annotation)
Listener->>Builder: build(typeContext or typeText)
Builder-->>Listener: CodeTypeRef (structured)
Listener->>Domain: construct/update object with TypeType, TypeValue, TypeRef / ReturnTypeRef
Domain->>Tests: serialize/assert (kotlinx.serialization / unit tests)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR introduces a comprehensive structured type reference system (CodeTypeRef) to enable cross-language type analysis in Chapi. The implementation adds optional type metadata fields to domain classes while maintaining full backward compatibility with existing code.
Changes:
- Added new
CodeTypeRefdata class with factory methods for 13+ type constructs (generics, unions, pointers, slices, etc.) - Extended
CodeField,CodeProperty, andCodeFunctionwith optionalTypeRef/ReturnTypeReffields - Implemented comprehensive test suite with 25 test cases covering all type kinds and backward compatibility
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.kt | New structured type reference system with factory methods and utility functions for cross-language type representation |
| chapi-domain/src/test/kotlin/chapi/domain/core/CodeTypeRefTest.kt | Comprehensive test suite covering all type kinds, helper methods, serialization, and backward compatibility |
| chapi-domain/src/main/kotlin/chapi/domain/core/CodeField.kt | Added optional TypeRef field for structured type analysis |
| chapi-domain/src/main/kotlin/chapi/domain/core/CodeProperty.kt | Added optional TypeRef field for structured type analysis |
| chapi-domain/src/main/kotlin/chapi/domain/core/CodeFunction.kt | Added optional ReturnTypeRef field for structured return type analysis |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fun pointer(pointeeType: CodeTypeRef, depth: Int = 1) = CodeTypeRef( | ||
| raw = "${"*".repeat(depth)}${pointeeType.raw}", | ||
| name = pointeeType.name, | ||
| kind = TypeRefKind.POINTER, | ||
| valueType = pointeeType, | ||
| pointerDepth = depth | ||
| ) |
There was a problem hiding this comment.
The pointer() factory method doesn't validate that depth parameter is positive. Negative or zero depth would create invalid pointer representations. Consider adding validation to ensure depth >= 1.
| fun pointer(pointeeType: CodeTypeRef, depth: Int = 1) = CodeTypeRef( | |
| raw = "${"*".repeat(depth)}${pointeeType.raw}", | |
| name = pointeeType.name, | |
| kind = TypeRefKind.POINTER, | |
| valueType = pointeeType, | |
| pointerDepth = depth | |
| ) | |
| fun pointer(pointeeType: CodeTypeRef, depth: Int = 1): CodeTypeRef { | |
| require(depth >= 1) { "Pointer depth must be >= 1, but was $depth" } | |
| return CodeTypeRef( | |
| raw = "${"*".repeat(depth)}${pointeeType.raw}", | |
| name = pointeeType.name, | |
| kind = TypeRefKind.POINTER, | |
| valueType = pointeeType, | |
| pointerDepth = depth | |
| ) | |
| } |
| fun isPrimitive(): Boolean { | ||
| val primitives = setOf( | ||
| "int", "long", "short", "byte", "float", "double", "boolean", "char", "void", | ||
| "Int", "Long", "Short", "Byte", "Float", "Double", "Boolean", "Char", "Unit", | ||
| "number", "string", "boolean", "undefined", "null", "void", "never", "any", "unknown", | ||
| "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize", | ||
| "f32", "f64", "bool", "str", "String", | ||
| "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "uintptr", | ||
| "float32", "float64", "complex64", "complex128", "rune", "error", "size_t" | ||
| ) | ||
| return name.lowercase() in primitives.map { it.lowercase() } | ||
| } |
There was a problem hiding this comment.
The isPrimitive() method creates a new set and performs lowercase mapping on every invocation, which is inefficient. Consider moving the primitives set to a companion object constant for better performance.
| fun isCollection(): Boolean { | ||
| val collectionNames = setOf( | ||
| "list", "set", "map", "hashmap", "hashset", "treemap", "treeset", | ||
| "arraylist", "linkedlist", "vector", "deque", "queue", "stack", | ||
| "collection", "iterable", "vec", "slice" | ||
| ) | ||
| return name.lowercase() in collectionNames || isSlice || kind == TypeRefKind.MAP | ||
| } |
There was a problem hiding this comment.
The isCollection() method creates a new set on every invocation, which is inefficient. Consider moving the collectionNames set to a companion object constant for better performance.
| fun array(elementType: CodeTypeRef, dimensions: Int = 1) = CodeTypeRef( | ||
| raw = "${elementType.raw}${"[]".repeat(dimensions)}", | ||
| name = elementType.name, | ||
| kind = TypeRefKind.ARRAY, | ||
| valueType = elementType, | ||
| arrayDimensions = dimensions | ||
| ) |
There was a problem hiding this comment.
The array() factory method doesn't validate that dimensions parameter is positive. Negative or zero dimensions would create invalid type representations. Consider adding validation to ensure dimensions >= 1.
| fun array(elementType: CodeTypeRef, dimensions: Int = 1) = CodeTypeRef( | |
| raw = "${elementType.raw}${"[]".repeat(dimensions)}", | |
| name = elementType.name, | |
| kind = TypeRefKind.ARRAY, | |
| valueType = elementType, | |
| arrayDimensions = dimensions | |
| ) | |
| fun array(elementType: CodeTypeRef, dimensions: Int = 1): CodeTypeRef { | |
| require(dimensions >= 1) { "Array dimensions must be >= 1, but was $dimensions" } | |
| return CodeTypeRef( | |
| raw = "${elementType.raw}${"[]".repeat(dimensions)}", | |
| name = elementType.name, | |
| kind = TypeRefKind.ARRAY, | |
| valueType = elementType, | |
| arrayDimensions = dimensions | |
| ) | |
| } |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #44 +/- ##
============================================
- Coverage 73.43% 67.39% -6.05%
- Complexity 1250 1386 +136
============================================
Files 69 77 +8
Lines 5120 6548 +1428
Branches 978 1248 +270
============================================
+ Hits 3760 4413 +653
- Misses 775 1402 +627
- Partials 585 733 +148 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add TypeRefBuilder for each language to convert type annotations to structured CodeTypeRef: TypeScript: - Union/intersection types - Generic types (Promise<T>, Array<T>, etc.) - Function types, tuples - Nullable types, mapped types Go: - Pointer types (*T, **T) - Slice types ([]T) - Map types (map[K]V) - Channel types (chan T, <-chan T, chan<- T) - Generic types (T[U]) Rust: - Reference types (&T, &'a T, &mut T) - Raw pointer types (*const T, *mut T) - Option<T> as nullable - Result<T, E>, Vec<T>, Box<T> - Tuple types, slice types - Trait bounds and lifetimes Java: - Primitive and class types - Array types (T[], T[][]) - Generic types (List<T>, Map<K,V>) - Optional<T> as nullable All existing tests pass.
Add comprehensive unit tests for TypeRef implementation: TypeScript tests: - Simple types, generics (Promise, Array, Map) - Union types, nullable types - Array bracket notation, tuples - Class field types Go tests: - Simple types, pointer types - Slice, map, channel types - Array types, interface types - Function return types Rust tests: - Simple types, reference types - Generic types (Vec, Option, Result, HashMap, Box) - Slice and array types - TypeRefBuilder string parsing tests Java tests: - Primitive and class types - Generic types (List, Map, Optional) - Array types (single and multi-dimensional) - Field types, TypeRefBuilder tests Also fix Rust TypeRefBuilder to correctly extract typePath from traitObjectTypeOneBound (simple types were being parsed as dyn types).
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt`:
- Around line 314-325: The generic-argument parser in GoTypeRefBuilder (inside
the branch that creates CodeTypeRef for TypeRefKind.GENERIC) incorrectly uses
argsRaw.split(",") which breaks on nested generics; change it to scan argsRaw
character-by-character tracking bracket depth (same approach used by
findMatchingBracket/map parsing) to split on commas only at depth 0, handle
empty args, trim each slice and call buildFromString for each part, and
construct the args list from those parsed segments instead of using split(",").
- Around line 187-207: The parameter/return parsing in GoTypeRefBuilder (in the
signature handling that calls parameterDecl().mapNotNull { build(param.type_())
}) only adds one entry per parameterDecl, losing arity when a decl has multiple
identifiers (e.g., func(a, b int)); update the logic in the parameter parsing
and the result.parameters() branch to iterate each parameterDecl's identifier
list and emit the type for each identifier (use the parameterDecl's identifiers
count or names and call build(param.type_()) that many times), so both
parameterTypes and the returns list reflect the true arity; keep creating a
single CodeTypeRef of kind TUPLE when multiple returns exist.
- Around line 267-280: The channel parsing in GoTypeRefBuilder incorrectly
requires spaces for directional channels; update the guard to also detect
raw.startsWith("<-chan") (no space) and change the element extraction to use
removePrefix("<-chan"), removePrefix("chan<-"), and removePrefix("chan")
followed by trimStart() so elementRaw is correct regardless of whitespace; keep
the existing mapping of directions ("receive" for "<-chan", "send" for "chan<-",
null for plain "chan") and then pass the trimmed elementRaw into buildFromString
for CodeTypeRef(valueType) while setting channelDirection accordingly.
In `@chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt`:
- Around line 612-624: The fieldDeclaration handling in JavaFullIdentListener
currently fetches the type by navigating declarators.parent.getChild(0) and
defensively casting; instead use the direct ctx.typeType() accessor: replace
that traversal with val typeType = ctx.typeType() and build the TypeRef via
JavaTypeRefBuilder.build(typeType), removing the safe-cast and fallback to
buildFromString; update the CodeField construction to use the resulting TypeRef
and the original type text/value variables so TypeRef structures remain
consistent with other methods (e.g., enterClassDeclaration,
enterFormalParameter).
In
`@chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.kt`:
- Around line 212-227: The CodeTypeRef returned for Array<T> currently sets kind
= TypeRefKind.ARRAY but leaves arrayDimensions at 0; update the return to set
arrayDimensions = 1 when kind == TypeRefKind.ARRAY (use the computed kind
variable) so that Array<T> and T[] are represented consistently; modify the
CodeTypeRef construction (fields raw, name/typeName, kind, args, valueType =
args.firstOrNull()) to also include arrayDimensions = if (kind ==
TypeRefKind.ARRAY) 1 else 0.
- Around line 256-270: The current function building function type refs ignores
rest parameters because it only maps ctx.parameterList()?.parameter(); update
the logic in TypeScriptTypeRefBuilder (the block that computes paramTypes and
returnType and returns CodeTypeRef) to also check
ctx.parameterList()?.restParameter() for a typeAnnotation, call build(...) on
that typeAnnotation (same helper used for
parameter.requiredParameter()/optionalParameter()), and append that resulting
type to the parameterTypes list so rest parameters (both sole restParameter and
trailing restParameter) are included in the resulting CodeTypeRef; keep
returnType produced by buildFromType(ctx.type_()) unchanged.
🧹 Nitpick comments (4)
chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt (2)
86-168: Consider semantic accuracy for map types.For
hashmapandbtreemap(line 136),valueType = args.firstOrNull()assigns the key type, not the value type. This could be semantically confusing for consumers of the API.If
CodeTypeRefsupports bothkeyTypeandvalueType, consider populating them appropriately for map types:"vec", "hashmap", "btreemap", "hashset", "btreeset" -> { CodeTypeRef( raw = raw, name = name, qualified = qualified, kind = TypeRefKind.GENERIC, args = args, - valueType = args.firstOrNull() + keyType = if (name.lowercase() in listOf("hashmap", "btreemap")) args.getOrNull(0) else null, + valueType = if (name.lowercase() in listOf("hashmap", "btreemap")) args.getOrNull(1) else args.firstOrNull() ) }
463-495: Missing curly brace tracking may break const generic parsing.The
splitTypeListfunction tracks<>,(), and[]for depth, but not{}. This could cause incorrect splits for const generics likeArray<T, { N + 1 }>or complex type expressions containing blocks.♻️ Suggested fix
private fun splitTypeList(str: String): List<String> { val result = mutableListOf<String>() var depth = 0 var current = StringBuilder() for (char in str) { when (char) { - '<', '(', '[' -> { + '<', '(', '[', '{' -> { depth++ current.append(char) } - '>', ')', ']' -> { + '>', ')', ']', '}' -> { depth-- current.append(char) }chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaTypeRefBuilder.kt (2)
147-156: Wildcards treated as SIMPLE types.The current approach handles wildcards (e.g.,
? extends T,? super T) by creating a SIMPLE CodeTypeRef with the raw text. This works but loses semantic information about bounded wildcards. Consider adding a dedicatedWILDCARDkind with bound information in a future iteration if downstream consumers need wildcard semantics.
161-210: Inconsistent handling of special types betweenbuild()andbuildFromString().The
build()method (viabuildClassOrInterfaceType) applies special handling forOptional,List,Map, etc., setting appropriatekind,nullable,valueType, andkeyTypefields. However,buildFromString()treats all generic types uniformly without this special handling.This could lead to inconsistent
CodeTypeRefstructures depending on the parsing path:
Optional<String>viabuild()→kind=NULLABLE, nullable=true, valueType=StringOptional<String>viabuildFromString()→kind=GENERIC, nullable=false, valueType=nullFor consistency, consider applying the same special-case logic here, similar to how
RustTypeRefBuilder.buildFromString()handlesOptionas nullable.♻️ Suggested enhancement for buildFromString
// Generic type raw.contains("<") && raw.endsWith(">") -> { val bracketStart = raw.indexOf('<') val name = raw.substring(0, bracketStart) val argsRaw = raw.substring(bracketStart + 1, raw.length - 1) val args = splitTypeList(argsRaw).map { buildFromString(it.trim()) } - CodeTypeRef( - raw = raw, - name = name, - kind = TypeRefKind.GENERIC, - args = args - ) + // Apply special handling for common Java generics + when (name.lowercase()) { + "optional" -> CodeTypeRef( + raw = raw, + name = name, + kind = TypeRefKind.NULLABLE, + args = args, + nullable = true, + valueType = args.firstOrNull() + ) + "list", "arraylist", "linkedlist", "set", "hashset", "treeset" -> CodeTypeRef( + raw = raw, + name = name, + kind = TypeRefKind.GENERIC, + args = args, + valueType = args.firstOrNull() + ) + "map", "hashmap", "treemap", "linkedhashmap" -> CodeTypeRef( + raw = raw, + name = name, + kind = TypeRefKind.GENERIC, + args = args, + keyType = args.getOrNull(0), + valueType = args.getOrNull(1) + ) + else -> CodeTypeRef( + raw = raw, + name = name, + kind = TypeRefKind.GENERIC, + args = args + ) + } }
| // Parse parameter types | ||
| val paramTypes = signature?.parameters()?.parameterDecl()?.mapNotNull { param -> | ||
| build(param.type_()) | ||
| } ?: listOf() | ||
|
|
||
| // Parse return type | ||
| val result = signature?.result() | ||
| val returnType = when { | ||
| result?.parameters() != null -> { | ||
| // Multiple return values - create tuple | ||
| val returns = result.parameters().parameterDecl().mapNotNull { build(it.type_()) } | ||
| if (returns.size == 1) returns.first() | ||
| else CodeTypeRef( | ||
| raw = result.text, | ||
| kind = TypeRefKind.TUPLE, | ||
| tupleElements = returns | ||
| ) | ||
| } | ||
| result?.type_() != null -> build(result.type_()) | ||
| else -> null | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "GoTypeRefBuilder.kt" | head -5Repository: phodal/chapi
Length of output: 122
🏁 Script executed:
find . -type f -name "*.g4" | grep -i go | head -10Repository: phodal/chapi
Length of output: 138
🏁 Script executed:
git ls-files | grep -i "\.g4$" | head -20Repository: phodal/chapi
Length of output: 959
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '180,215p'Repository: phodal/chapi
Length of output: 1541
🏁 Script executed:
cat -n chapi-ast-go/src/main/antlr/GoParser.g4 | grep -A 20 "parameterDecl"Repository: phodal/chapi
Length of output: 897
🏁 Script executed:
cat -n chapi-ast-go/src/main/antlr/GoParser.g4 | grep -B 5 -A 15 "identifierList"Repository: phodal/chapi
Length of output: 4212
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | head -50Repository: phodal/chapi
Length of output: 1861
🏁 Script executed:
grep -r "parameterDecl" chapi-ast-go/src/main/kotlin/chapi/ast/goast/ --include="*.kt" | head -20Repository: phodal/chapi
Length of output: 1017
🏁 Script executed:
find chapi-ast-go -name "GoParser.kt" -type fRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoAstListener.kt | grep -B 10 -A 10 "parameterDecl"Repository: phodal/chapi
Length of output: 1475
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt | grep -B 15 -A 15 "parameterDecl"Repository: phodal/chapi
Length of output: 1986
🏁 Script executed:
# Check if there are generated parser files to understand the parameterDecl API structure
find chapi-ast-go -name "GoParser.java" -o -name "*ParameterDecl*" | head -10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for how identifierList is used in the codebase to understand the API
rg "identifierList\(\)" chapi-ast-go/src/main/kotlin/chapi/ast/goast/ -A 3 -B 1Repository: phodal/chapi
Length of output: 3654
🏁 Script executed:
# Check how other parts of the code handle multiple identifiers
rg "IDENTIFIER\(\)" chapi-ast-go/src/main/kotlin/chapi/ast/goast/ -B 2 -A 2 | head -40Repository: phodal/chapi
Length of output: 4059
🏁 Script executed:
# Look for tests that might show function type handling
find chapi-ast-go -name "*Test*.kt" -type f | head -10Repository: phodal/chapi
Length of output: 387
🏁 Script executed:
# Check if there are Go examples in test resources
find chapi-ast-go -name "*.go" -type f | head -20Repository: phodal/chapi
Length of output: 1173
🏁 Script executed:
# Check a Go example file with function declarations
cat chapi-ast-go/src/test/resources/examples/function.goRepository: phodal/chapi
Length of output: 99
🏁 Script executed:
# Check if there are any tests related to function types or type references
grep -r "FunctionType\|parameterDecl\|IDENTIFIER()" chapi-ast-go/src/test/kotlin/ -B 2 -A 5 | head -60Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Look at GoGenericsTest to understand how types are tested
cat -n chapi-ast-go/src/test/kotlin/chapi/ast/goast/GoGenericsTest.kt | head -80Repository: phodal/chapi
Length of output: 2706
🏁 Script executed:
# Check the full GoFullIdentListener to see how it handles multiple identifiers
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt | sed -n '255,270p'Repository: phodal/chapi
Length of output: 1126
🏁 Script executed:
# Check if there are any tests that verify function type handling with multiple parameters
grep -r "func.*,.*int\|flatMap\|mapNotNull" chapi-ast-go/src/test/ -B 2 -A 2Repository: phodal/chapi
Length of output: 12122
🏁 Script executed:
# Look for any actual test cases with multiple identifiers in parameters
find chapi-ast-go/src/test/resources -name "*.go" -exec grep -l "func.*,.*int\|a.*b.*int" {} \;Repository: phodal/chapi
Length of output: 555
🏁 Script executed:
cat chapi-ast-go/src/test/resources/examples/params.goRepository: phodal/chapi
Length of output: 296
🏁 Script executed:
# Now let's check how buildParameters handles this case
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt | sed -n '144,160p'Repository: phodal/chapi
Length of output: 841
🏁 Script executed:
# Let's trace through what the current code does with "a, b int32"
# Check processingType to understand how it extracts identifiers
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoFullIdentListener.kt | sed -n '158,175p'Repository: phodal/chapi
Length of output: 807
🏁 Script executed:
# Let's check if there are any tests that validate parameter counting
grep -r "Parameters.size\|parameters\.size" chapi-ast-go/src/test/kotlin/ -B 5 -A 5Repository: phodal/chapi
Length of output: 13999
🏁 Script executed:
# Check if there are any tests for GoTypeRefBuilder specifically
find chapi-ast-go -name "*TypeRef*" -o -name "*Type*Test*" | head -10Repository: phodal/chapi
Length of output: 120
🏁 Script executed:
# Let's look at how GoTypeRefBuilder is tested or used with function types
grep -r "GoTypeRefBuilder\|FunctionType" chapi-ast-go/src/test/ -B 3 -A 3Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Check if identifierList().IDENTIFIER() method exists (for the proposed fix)
grep -r "\.IDENTIFIER()" chapi-ast-go/src/main/kotlin/ | head -10Repository: phodal/chapi
Length of output: 1407
🏁 Script executed:
# Let's test the actual problem: create a simple Go code with function type and see what happens
cat > /tmp/test_functype.go << 'EOF'
package main
// Function type with combined parameters
type MyFunc func(a, b int) (x, y int)
func main() {}
EOF
# Check params.go to see if it has a function type test
cat chapi-ast-go/src/test/resources/specific/struct_function.go | head -20Repository: phodal/chapi
Length of output: 338
🏁 Script executed:
# Let's verify the actual impact by checking if IDENTIFIER() method exists on IdentifierListContext
grep -r "identifierList()?.IDENTIFIER()" chapi-ast-go/src/main/kotlin/ -B 2 -A 2Repository: phodal/chapi
Length of output: 628
🏁 Script executed:
# Now let's trace through exactly what happens with a function type that has combined params
# Look for where GoTypeRefBuilder is called for function types
grep -r "buildFunctionType\|FunctionTypeContext" chapi-ast-go/src/main/kotlin/ -B 3 -A 3Repository: phodal/chapi
Length of output: 1521
🏁 Script executed:
# Check if there are any tests that create function types with combined parameters
grep -r "func.*\(.*,.*\).*func\|FunctionType.*param\|buildFunctionType" chapi-ast-go/ -B 2 -A 5Repository: phodal/chapi
Length of output: 3876
🏁 Script executed:
# Let's verify the exact context - look at the buildFunctionType method more carefully
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '184,220p'Repository: phodal/chapi
Length of output: 1645
🏁 Script executed:
# Let's create a concrete test to verify the bug exists
cat > /tmp/test_functype_bug.kt << 'EOF'
import chapi.ast.goast.GoAnalyser
// Test function type with combined parameters
val code = """
package main
type MyFunc func(a, b int) (x, y int)
"""
val codeFile = GoAnalyser().analysis(code, "test.go")
val typeRef = codeFile.Types[0].TypeRef
println("Function type: $typeRef")
println("Parameter count: ${typeRef?.parameterTypes?.size}")
println("Return type: ${typeRef?.returnType}")
EOF
# This would need the actual test runner, but let's verify the logic another wayRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's verify the parsing by checking what parameterDecl structure looks like
cat -n chapi-ast-go/src/main/antlr/GoParser.g4 | sed -n '388,400p'Repository: phodal/chapi
Length of output: 578
🏁 Script executed:
# Check if there's test coverage for function type parameter counting
grep -r "parameterTypes\|returnType" chapi-ast-go/src/test/ -B 2 -A 2 | head -40Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's check if buildFunctionType is actually called and tested
grep -r "buildFunctionType\|func.*func.*int.*int\|MyFunc\|Finalsalary" chapi-ast-go/src/test/ -B 3 -A 5Repository: phodal/chapi
Length of output: 1454
🏁 Script executed:
# Let's look at the struct_function test which has function types
cat chapi-ast-go/src/test/resources/specific/struct_function.goRepository: phodal/chapi
Length of output: 862
🏁 Script executed:
# Check if there's a test that validates function type parsing
grep -r "FunctionType\|FUNCTION.*kind\|TypeRefKind.FUNCTION" chapi-ast-go/src/test/ -B 3 -A 3Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's verify the actual test code for the struct_function.go file
grep -r "struct_function\|Finalsalary" chapi-ast-go/src/test/ -B 5 -A 10Repository: phodal/chapi
Length of output: 2010
🏁 Script executed:
# Check where types are processed - to see if function types are parsed through buildFunctionType
grep -r "type.*func\|Type.*func" chapi-ast-go/src/test/kotlin/ -B 2 -A 5Repository: phodal/chapi
Length of output: 972
🏁 Script executed:
# Look for where TypeRefBuilder.build is called to understand the context
grep -r "GoTypeRefBuilder.build\|buildFunctionType" chapi-ast-go/src/main/kotlin/ -B 3 -A 3Repository: phodal/chapi
Length of output: 4395
🏁 Script executed:
# Check if there's a test that validates the actual parsing of function types
grep -r "parameterTypes.*size\|returnType.*size" chapi-ast-go/ -B 3 -A 3Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's look at the actual CodeTypeRef class to understand what parameterTypes represents
find chapi-ast-go -name "CodeTypeRef*" -o -name "*TypeRef*" | grep -v test | grep -v antlrRepository: phodal/chapi
Length of output: 120
🏁 Script executed:
# Check the domain module for CodeTypeRef
find . -name "CodeTypeRef.kt" -o -name "*TypeRef*.kt" | grep -v test | grep -v antlr | head -5Repository: phodal/chapi
Length of output: 423
🏁 Script executed:
# Check the CodeTypeRef class definition
cat chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.ktRepository: phodal/chapi
Length of output: 8265
🏁 Script executed:
# Now let's look for actual test files that test GoTypeRefBuilder or function types
find chapi-ast-go/src/test -name "*.kt" -exec grep -l "parameterTypes\|returnType\|FunctionType" {} \;Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's also check if there are test resources with function type declarations with combined parameters
find chapi-ast-go/src/test/resources -name "*.go" -exec grep -l "func.*,.*)" {} \;Repository: phodal/chapi
Length of output: 432
🏁 Script executed:
# Let's trace through the actual test to understand if parameterTypes is ever validated
grep -r "parameterTypes" chapi-domain/ -B 2 -A 2Repository: phodal/chapi
Length of output: 1038
🏁 Script executed:
# Check if buildFunctionType is called anywhere in the codebase
grep -r "buildFunctionType\|\.parameterTypes\|\.returnType" chapi-ast-go/ --include="*.kt" | grep -v test | head -20Repository: phodal/chapi
Length of output: 352
🏁 Script executed:
# Verify the test file that uses struct_function.go
find chapi-ast-go/src/test -name "*.kt" | xargs grep -l "struct_function"Repository: phodal/chapi
Length of output: 40
Expand function type parameter/return arity to match identifier count.
For Go signatures like func(a, b int) or return types like (x, y int), each parameterDecl can specify multiple identifiers sharing a single type. The current mapNotNull logic adds only one entry per parameterDecl, undercounting the actual parameter/return arity. This misrepresents function type signatures.
✅ Proposed fix to expand by identifier count
- val paramTypes = signature?.parameters()?.parameterDecl()?.mapNotNull { param ->
- build(param.type_())
- } ?: listOf()
+ val paramTypes = signature?.parameters()?.parameterDecl()?.flatMap { param ->
+ val typeRef = build(param.type_())
+ val count = param.identifierList()?.IDENTIFIER()?.size ?: 1
+ if (typeRef == null) emptyList() else List(count) { typeRef }
+ } ?: emptyList()
...
- val returns = result.parameters().parameterDecl().mapNotNull { build(it.type_()) }
+ val returns = result.parameters().parameterDecl().flatMap { param ->
+ val typeRef = build(param.type_())
+ val count = param.identifierList()?.IDENTIFIER()?.size ?: 1
+ if (typeRef == null) emptyList() else List(count) { typeRef }
+ }🤖 Prompt for AI Agents
In `@chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt` around
lines 187 - 207, The parameter/return parsing in GoTypeRefBuilder (in the
signature handling that calls parameterDecl().mapNotNull { build(param.type_())
}) only adds one entry per parameterDecl, losing arity when a decl has multiple
identifiers (e.g., func(a, b int)); update the logic in the parameter parsing
and the result.parameters() branch to iterate each parameterDecl's identifier
list and emit the type for each identifier (use the parameterDecl's identifiers
count or names and call build(param.type_()) that many times), so both
parameterTypes and the returns list reflect the true arity; keep creating a
single CodeTypeRef of kind TUPLE when multiple returns exist.
| raw.startsWith("chan ") || raw.startsWith("chan<-") || raw.startsWith("<-chan ") -> { | ||
| val (direction, elementRaw) = when { | ||
| raw.startsWith("<-chan ") -> "receive" to raw.drop(7) | ||
| raw.startsWith("chan<- ") -> "send" to raw.drop(7) | ||
| raw.startsWith("chan ") -> null to raw.drop(5) | ||
| else -> null to raw | ||
| } | ||
| CodeTypeRef( | ||
| raw = raw, | ||
| name = "chan", | ||
| kind = TypeRefKind.CHANNEL, | ||
| valueType = buildFromString(elementRaw), | ||
| channelDirection = direction | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd chapi-ast-go && find . -name "GoTypeRefBuilder.kt" -type f | head -5Repository: phodal/chapi
Length of output: 109
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '267,280p'Repository: phodal/chapi
Length of output: 845
🌐 Web query:
Go channel syntax chan<- chan receive send valid
💡 Result:
Short answer: Yes — but be careful with parsing. Go accepts directional channel types, and you can have channels whose element types are channels. However the parser associates the <- with the nearest chan, so
- chan<- chan int — valid: a send-only channel whose element is a (bidirectional) chan int. [1][2]
- <-chan chan int — valid: a receive-only channel whose element is a (bidirectional) chan int. [1][2]
If you want "a channel of receive-only channels" (i.e. the element type is <-chan T) you must avoid the parser ambiguity. Use a named type or parentheses/alias:
- type Recv <-chan int; c := make(chan Recv) — works. [3]
(Attempts like make(chan <-chan int) are parsed as chan<- chan int and will not give the intended type; see the discussion and examples.) [3]
References:
- Go channel directions overview. [2]
- Go101 channels (directional channels). [1]
- StackOverflow: "How to create a channel of receive-only channels?" (explains the parsing ambiguity and shows using a type alias). [5]
🏁 Script executed:
cat > /tmp/test_parsing.py << 'EOF'
# Test current parsing logic
def parse_current(raw):
if raw.startswith("<-chan "):
return "receive", raw[7:] # drop 7
elif raw.startswith("chan<- "):
return "send", raw[7:] # drop 7
elif raw.startswith("chan "):
return None, raw[5:] # drop 5
else:
return None, raw
def parse_proposed(raw):
if raw.startswith("<-chan"):
return "receive", raw.removeprefix("<-chan").lstrip()
elif raw.startswith("chan<-"):
return "send", raw.removeprefix("chan<-").lstrip()
elif raw.startswith("chan "):
return None, raw.removeprefix("chan").lstrip()
else:
return None, raw
test_cases = [
"chan int", # Current OK: (None, "int")
"chan<- int", # Current OK: (send, "int")
"chan<-int", # Current BUG: falls to else -> (None, "chan<-int")
"<-chan int", # Current OK: (receive, "int")
"<-chanint", # Guard FAILS: doesn't match "<-chan " guard
"chan[]int", # Test array element
]
print("CURRENT PARSING:")
for test in test_cases:
direction, element = parse_current(test)
print(f" {test:20} -> direction={direction}, element={element}")
print("\nPROPOSED PARSING:")
for test in test_cases:
direction, element = parse_proposed(test)
print(f" {test:20} -> direction={direction}, element={element}")
EOF
python /tmp/test_parsing.pyRepository: phodal/chapi
Length of output: 142
🏁 Script executed:
find chapi-ast-go -name "*Test*.kt" -o -name "*test*.kt" | grep -i go | head -10Repository: phodal/chapi
Length of output: 387
🏁 Script executed:
find chapi-ast-go -type f -name "*.kt" | xargs grep -l "GoTypeRefBuilder" | head -5Repository: phodal/chapi
Length of output: 250
🏁 Script executed:
grep -n "chan" chapi-ast-go/src/test/kotlin/chapi/ast/goast/GoAnalyserTest.kt | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
grep -n "buildFromString\|TypeRef" chapi-ast-go/src/test/kotlin/chapi/ast/goast/GoAnalyserTest.kt | head -10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '260,290p'Repository: phodal/chapi
Length of output: 1672
🏁 Script executed:
cat -n chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '1,50p' | head -30Repository: phodal/chapi
Length of output: 1118
🏁 Script executed:
cat > /tmp/verify_fix.py << 'EOF'
# Simulating the parsing logic
def current_logic(raw):
"""Current code behavior"""
print(f"Testing: '{raw}'")
# Guard check
if raw.startswith("chan ") or raw.startswith("chan<-") or raw.startswith("<-chan "):
print(f" ✓ Guard passed")
# When pattern matching
if raw.startswith("<-chan "):
direction, elem = "receive", raw[7:]
elif raw.startswith("chan<- "):
direction, elem = "send", raw[7:]
elif raw.startswith("chan "):
direction, elem = None, raw[5:]
else:
direction, elem = None, raw # FALLBACK
print(f" → direction={direction}, elementRaw='{elem}'")
return direction, elem
else:
print(f" ✗ Guard failed - not handled")
return None, None
def proposed_logic(raw):
"""Proposed fix using removePrefix + trimStart"""
print(f"Testing: '{raw}'")
# Guard would need updating too
if raw.startswith("chan") or raw.startswith("<-chan"):
print(f" ✓ Guard passed")
if raw.startswith("<-chan"):
direction = "receive"
elem = raw.removeprefix("<-chan").lstrip()
elif raw.startswith("chan<-"):
direction = "send"
elem = raw.removeprefix("chan<-").lstrip()
elif raw.startswith("chan "):
direction = None
elem = raw.removeprefix("chan").lstrip()
else:
direction, elem = None, raw
print(f" → direction={direction}, elementRaw='{elem}'")
return direction, elem
else:
print(f" ✗ Guard failed")
return None, None
test_cases = [
"chan int",
"chan<- int",
"chan<-int", # BUG CASE
"<-chan int",
"<-chanint", # NOT HANDLED
]
print("=== CURRENT CODE ===")
for test in test_cases:
current_logic(test)
print()
print("\n=== PROPOSED FIX ===")
for test in test_cases:
proposed_logic(test)
print()
EOF
python /tmp/verify_fix.pyRepository: phodal/chapi
Length of output: 852
Handle channel types without whitespace (chan<-T, <-chanT).
Go allows channel types without spaces after directional operators, but the current code only matches patterns with required spaces. This causes chan<-int to be mis-parsed as an undirected channel, and <-chanint is not recognized by the guard condition at all.
Update the guard to check for <-chan (without space) and use removePrefix().trimStart() in the pattern matching to correctly extract element types regardless of whitespace:
Proposed fix
- raw.startsWith("chan ") || raw.startsWith("chan<-") || raw.startsWith("<-chan ") -> {
+ raw.startsWith("chan ") || raw.startsWith("chan<-") || raw.startsWith("<-chan") -> {
val (direction, elementRaw) = when {
- raw.startsWith("<-chan ") -> "receive" to raw.drop(7)
- raw.startsWith("chan<- ") -> "send" to raw.drop(7)
- raw.startsWith("chan ") -> null to raw.drop(5)
+ raw.startsWith("<-chan") -> "receive" to raw.removePrefix("<-chan").trimStart()
+ raw.startsWith("chan<-") -> "send" to raw.removePrefix("chan<-").trimStart()
+ raw.startsWith("chan ") -> null to raw.removePrefix("chan").trimStart()
else -> null to raw
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| raw.startsWith("chan ") || raw.startsWith("chan<-") || raw.startsWith("<-chan ") -> { | |
| val (direction, elementRaw) = when { | |
| raw.startsWith("<-chan ") -> "receive" to raw.drop(7) | |
| raw.startsWith("chan<- ") -> "send" to raw.drop(7) | |
| raw.startsWith("chan ") -> null to raw.drop(5) | |
| else -> null to raw | |
| } | |
| CodeTypeRef( | |
| raw = raw, | |
| name = "chan", | |
| kind = TypeRefKind.CHANNEL, | |
| valueType = buildFromString(elementRaw), | |
| channelDirection = direction | |
| ) | |
| raw.startsWith("chan ") || raw.startsWith("chan<-") || raw.startsWith("<-chan") -> { | |
| val (direction, elementRaw) = when { | |
| raw.startsWith("<-chan") -> "receive" to raw.removePrefix("<-chan").trimStart() | |
| raw.startsWith("chan<-") -> "send" to raw.removePrefix("chan<-").trimStart() | |
| raw.startsWith("chan ") -> null to raw.removePrefix("chan").trimStart() | |
| else -> null to raw | |
| } | |
| CodeTypeRef( | |
| raw = raw, | |
| name = "chan", | |
| kind = TypeRefKind.CHANNEL, | |
| valueType = buildFromString(elementRaw), | |
| channelDirection = direction | |
| ) |
🤖 Prompt for AI Agents
In `@chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt` around
lines 267 - 280, The channel parsing in GoTypeRefBuilder incorrectly requires
spaces for directional channels; update the guard to also detect
raw.startsWith("<-chan") (no space) and change the element extraction to use
removePrefix("<-chan"), removePrefix("chan<-"), and removePrefix("chan")
followed by trimStart() so elementRaw is correct regardless of whitespace; keep
the existing mapping of directions ("receive" for "<-chan", "send" for "chan<-",
null for plain "chan") and then pass the trimmed elementRaw into buildFromString
for CodeTypeRef(valueType) while setting channelDirection accordingly.
| raw.contains("[") && raw.endsWith("]") -> { | ||
| val bracketStart = raw.indexOf('[') | ||
| val name = raw.substring(0, bracketStart) | ||
| val argsRaw = raw.substring(bracketStart + 1, raw.length - 1) | ||
| val args = argsRaw.split(",").map { buildFromString(it.trim()) } | ||
| CodeTypeRef( | ||
| raw = raw, | ||
| name = name, | ||
| kind = TypeRefKind.GENERIC, | ||
| args = args | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "GoTypeRefBuilder.kt" -type fRepository: phodal/chapi
Length of output: 122
🏁 Script executed:
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '310,350p'Repository: phodal/chapi
Length of output: 1461
🏁 Script executed:
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | head -n 350 | tail -n +300Repository: phodal/chapi
Length of output: 1962
🏁 Script executed:
# Search for other split(",") patterns in the file that might have similar issues
rg 'split\s*\(' ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt -nRepository: phodal/chapi
Length of output: 140
🏁 Script executed:
# Look at the broader context of the buildFromString function
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | head -n 320 | tail -n +290Repository: phodal/chapi
Length of output: 1644
🏁 Script executed:
# Get the full buildFromString function to understand context
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | head -n 331 | tail -n +270Repository: phodal/chapi
Length of output: 3028
🏁 Script executed:
# Check if there are any tests for this function
find . -name "*GoTypeRefBuilder*Test*" -o -name "*Test*GoTypeRefBuilder*" | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for test files in the go AST directory
find ./chapi-ast-go -name "*Test.kt" -o -name "*test.kt" | head -20Repository: phodal/chapi
Length of output: 397
🏁 Script executed:
# Check the generics test file
cat -n ./chapi-ast-go/src/test/kotlin/chapi/ast/goast/GoGenericsTest.ktRepository: phodal/chapi
Length of output: 7727
🏁 Script executed:
# Search for tests that use GoTypeRefBuilder or buildFromString
rg 'GoTypeRefBuilder|buildFromString' ./chapi-ast-go/src/test -A 3 -B 3Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for usages of buildFromString in the codebase
rg 'buildFromString' ./chapi-ast-go/src -A 2 -B 2Repository: phodal/chapi
Length of output: 4325
🏁 Script executed:
# Check the entire file length to understand the "Also applies to: 333-344" reference
wc -l ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 126
🏁 Script executed:
# Search for test cases or usages with nested generics like Foo[Bar[Baz], Qux]
rg '\[\w+\[.*\].*\]' ./chapi-ast-go -A 2 -B 2Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let me verify the actual structure by examining how the function is meant to be used
# Check if there are real-world examples or tests with nested generic types
rg 'Map\[' ./chapi-ast-go/src/test -A 2 -B 2Repository: phodal/chapi
Length of output: 591
🏁 Script executed:
# Check if there are other similar patterns or if the split issue repeats elsewhere
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | grep -E 'split|substring' -nRepository: phodal/chapi
Length of output: 673
🏁 Script executed:
# Let me look more carefully at the map parsing to see if it uses findMatchingBracket
cat -n ./chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt | sed -n '245,265p'Repository: phodal/chapi
Length of output: 1030
Fix generic argument parsing to handle nested type parameters.
The split(",") approach at line 318 fails for nested generics (e.g., Foo[Bar[Baz], Qux]) because commas inside brackets are not tracked, leading to incorrect type trees. The codebase already uses bracket-depth tracking for map type parsing via findMatchingBracket()—apply the same pattern here.
Proposed fix
- val args = argsRaw.split(",").map { buildFromString(it.trim()) }
+ val args = splitTypeArgs(argsRaw).map { buildFromString(it.trim()) }
CodeTypeRef(
raw = raw,
name = name,
kind = TypeRefKind.GENERIC,
args = args
)
}
...
+ private fun splitTypeArgs(argsRaw: String): List<String> {
+ val result = mutableListOf<String>()
+ val buf = StringBuilder()
+ var depth = 0
+ for (ch in argsRaw) {
+ when (ch) {
+ '[' -> { depth++; buf.append(ch) }
+ ']' -> { depth--; buf.append(ch) }
+ ',' -> if (depth == 0) {
+ result += buf.toString()
+ buf.setLength(0)
+ } else {
+ buf.append(ch)
+ }
+ else -> buf.append(ch)
+ }
+ }
+ if (buf.isNotEmpty()) result += buf.toString()
+ return result
+ }🤖 Prompt for AI Agents
In `@chapi-ast-go/src/main/kotlin/chapi/ast/goast/GoTypeRefBuilder.kt` around
lines 314 - 325, The generic-argument parser in GoTypeRefBuilder (inside the
branch that creates CodeTypeRef for TypeRefKind.GENERIC) incorrectly uses
argsRaw.split(",") which breaks on nested generics; change it to scan argsRaw
character-by-character tracking bracket depth (same approach used by
findMatchingBracket/map parsing) to split on commas only at depth 0, handle
empty args, trim each slice and call buildFromString for each part, and
construct the args list from those parsed segments instead of using split(",").
| // Build TypeRef from the field's typeType context | ||
| val fieldTypeCtx = (typeType as? JavaParser.TypeTypeContext) | ||
| val typeRef = fieldTypeCtx?.let { JavaTypeRefBuilder.build(it) } | ||
| ?: JavaTypeRefBuilder.buildFromString(typeTypeText) | ||
|
|
||
| val field = CodeField( | ||
| typeTypeText, | ||
| typeValue, | ||
| typeKey, | ||
| Modifiers = listOf(), | ||
| Annotations = this.currentAnnotations | ||
| Annotations = this.currentAnnotations, | ||
| TypeRef = typeRef | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for FieldDeclarationContext definition and its methods
rg "class FieldDeclarationContext" -A 30 --type=kotlin -g "**/JavaParser*.kt"Repository: phodal/chapi
Length of output: 204
🏁 Script executed:
# Search for the grammar file defining fieldDeclaration
find . -name "*.g4" -type f | head -10Repository: phodal/chapi
Length of output: 514
🏁 Script executed:
# Look at the context of the problematic code to understand the parent hierarchy
rg -B 10 "declarators.parent.getChild" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt | head -30Repository: phodal/chapi
Length of output: 454
🏁 Script executed:
# Search for the fieldDeclaration rule in the Java grammar
rg -A 10 "fieldDeclaration" chapi-ast-java/src/main/antlr/JavaParser.g4 | head -30Repository: phodal/chapi
Length of output: 609
🏁 Script executed:
# Look for the parent class structure of FieldDeclarationContext
rg "typeType" chapi-ast-java/src/main/antlr/JavaParser.g4 | head -20Repository: phodal/chapi
Length of output: 1280
🏁 Script executed:
# Check the exact grammar rule definition
grep -A 5 "fieldDeclaration" chapi-ast-java/src/main/antlr/JavaParser.g4Repository: phodal/chapi
Length of output: 359
🏁 Script executed:
# Search for FieldDeclarationContext in the generated parser to see what methods it has
rg "class FieldDeclarationContext" -A 50 chapi-ast-java/src/main/antlr/JavaParser.kt | head -80Repository: phodal/chapi
Length of output: 139
🏁 Script executed:
# Also search for typeType() method definition in FieldDeclarationContext
rg "fun typeType\(\)" chapi-ast-java/src/main/antlr/JavaParser.kt | head -10Repository: phodal/chapi
Length of output: 139
🏁 Script executed:
# Find all generated parser files in the Java module
find chapi-ast-java -name "*Parser*.kt" -o -name "*Parser*.java" | head -20Repository: phodal/chapi
Length of output: 120
🏁 Script executed:
# Check the structure of the chapi-ast-java directory
ls -la chapi-ast-java/src/main/Repository: phodal/chapi
Length of output: 326
🏁 Script executed:
# List files in the antlr directory
ls -la chapi-ast-java/src/main/antlr/Repository: phodal/chapi
Length of output: 296
🏁 Script executed:
# Check if there's a build directory with generated parser
find chapi-ast-java -name "*.java" -path "*JavaParser*" | head -10Repository: phodal/chapi
Length of output: 120
🏁 Script executed:
# Search for FieldDeclarationContext in Java files
rg "class FieldDeclarationContext" chapi-ast-java/ -A 50 | head -100Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for the actual listener method signature to confirm ctx type
rg "fun enterFieldDeclaration" chapi-ast-java/ -A 5Repository: phodal/chapi
Length of output: 733
🏁 Script executed:
# Check if there are any usages of ctx.typeType() pattern elsewhere in the listener
rg "\.typeType\(\)" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt | head -20Repository: phodal/chapi
Length of output: 479
🏁 Script executed:
# Let's verify the exact line where ctx.typeType() is used in the enterFormalParameter method
rg -B 5 -A 5 "fun enterFormalParameter" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt | head -30Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Check if the grammar-generated context classes are actually available at runtime
# by looking for how JavaParser types are imported
rg "import.*JavaParser" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.ktRepository: phodal/chapi
Length of output: 89
🏁 Script executed:
# Check what JavaParser import refers to - is it generated or custom?
ls -la chapi-ast-java/src/main/java/chapi/ast/antlr/Repository: phodal/chapi
Length of output: 235
🏁 Script executed:
# Look for other enterX methods in JavaFullIdentListener to see the pattern
rg "override fun enter" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt | head -20Repository: phodal/chapi
Length of output: 1255
🏁 Script executed:
# Search for direct ctx.typeType() usages and compare with the getChild(0) approach
rg "ctx\.typeType\(\)" chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt -B 3 -A 3 | head -40Repository: phodal/chapi
Length of output: 693
Use direct context accessor ctx.typeType() instead of declarators.parent.getChild(0).
The grammar rule fieldDeclaration : typeType variableDeclarators ';' generates a direct typeType() accessor on FieldDeclarationContext. The current code unnecessarily traverses through declarators.parent.getChild(0), which requires defensive casting and a fallback path.
Other methods in the same listener (e.g., enterClassDeclaration, enterFormalParameter) correctly use ctx.typeType() directly. Replace the current approach with:
val typeType = ctx.typeType()
val typeRef = JavaTypeRefBuilder.build(typeType)This eliminates the safe cast, fallback logic, and potential inconsistency in TypeRef structures.
🤖 Prompt for AI Agents
In `@chapi-ast-java/src/main/kotlin/chapi/ast/javaast/JavaFullIdentListener.kt`
around lines 612 - 624, The fieldDeclaration handling in JavaFullIdentListener
currently fetches the type by navigating declarators.parent.getChild(0) and
defensively casting; instead use the direct ctx.typeType() accessor: replace
that traversal with val typeType = ctx.typeType() and build the TypeRef via
JavaTypeRefBuilder.build(typeType), removing the safe-cast and fallback to
buildFromString; update the CodeField construction to use the resulting TypeRef
and the original type text/value variables so TypeRef structures remain
consistent with other methods (e.g., enterClassDeclaration,
enterFormalParameter).
| // Special handling for common generic types | ||
| val kind = when (typeName.lowercase()) { | ||
| "array" -> TypeRefKind.ARRAY | ||
| "promise", "observable" -> TypeRefKind.GENERIC | ||
| "map", "set", "weakmap", "weakset" -> TypeRefKind.GENERIC | ||
| "partial", "required", "readonly", "pick", "omit", "record" -> TypeRefKind.MAPPED | ||
| else -> TypeRefKind.GENERIC | ||
| } | ||
|
|
||
| return CodeTypeRef( | ||
| raw = raw, | ||
| name = typeName, | ||
| kind = kind, | ||
| args = args, | ||
| valueType = args.firstOrNull() // For Array<T>, store T as valueType | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the target file and verify it exists
find . -name "TypeScriptTypeRefBuilder.kt" -type fRepository: phodal/chapi
Length of output: 146
🏁 Script executed:
#!/bin/bash
# Get line count to understand file size
wc -l chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 148
🏁 Script executed:
#!/bin/bash
# Read the specific lines mentioned in the review
sed -n '212,227p' chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 753
🏁 Script executed:
#!/bin/bash
# Find CodeTypeRef class definition
find . -name "*.kt" -type f | xargs grep -l "class CodeTypeRef" | head -5Repository: phodal/chapi
Length of output: 187
🏁 Script executed:
#!/bin/bash
# Search for T[] array handling in TypeScriptTypeRefBuilder
grep -n "T\[\]" chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
#!/bin/bash
# Search for arrayDimensions usage in the codebase
rg "arrayDimensions" -t kt | head -20Repository: phodal/chapi
Length of output: 82
🏁 Script executed:
#!/bin/bash
# Read CodeTypeRef class definition
cat -n chapi-domain/src/main/kotlin/chapi/domain/core/CodeTypeRef.ktRepository: phodal/chapi
Length of output: 10015
🏁 Script executed:
#!/bin/bash
# Search for arrayDimensions usage (without -t flag)
rg "arrayDimensions" --type-list | head -5Repository: phodal/chapi
Length of output: 445
🏁 Script executed:
#!/bin/bash
# Search for arrayDimensions in kotlin files
rg "arrayDimensions" . -A 2 -B 2 | head -40Repository: phodal/chapi
Length of output: 3687
🏁 Script executed:
#!/bin/bash
# Search for other TypeRefKind.ARRAY usages
rg "TypeRefKind\.ARRAY" -A 3 -B 3 | head -60Repository: phodal/chapi
Length of output: 6488
🏁 Script executed:
#!/bin/bash
# View more context around the problematic code in TypeScriptTypeRefBuilder.kt
sed -n '200,240p' chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1660
Set arrayDimensions for Array<T> references.
Array<T> is mapped to TypeRefKind.ARRAY but leaves arrayDimensions at the default value of 0, inconsistent with T[] syntax which explicitly sets it to 1. This can cause downstream consumers to misinterpret single-dimensional arrays. Set arrayDimensions = 1 when kind == TypeRefKind.ARRAY.
Proposed fix
- return CodeTypeRef(
- raw = raw,
- name = typeName,
- kind = kind,
- args = args,
- valueType = args.firstOrNull() // For Array<T>, store T as valueType
- )
+ val arrayDims = if (kind == TypeRefKind.ARRAY) 1 else 0
+ return CodeTypeRef(
+ raw = raw,
+ name = typeName,
+ kind = kind,
+ args = args,
+ valueType = args.firstOrNull(), // For Array<T>, store T as valueType
+ arrayDimensions = arrayDims
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Special handling for common generic types | |
| val kind = when (typeName.lowercase()) { | |
| "array" -> TypeRefKind.ARRAY | |
| "promise", "observable" -> TypeRefKind.GENERIC | |
| "map", "set", "weakmap", "weakset" -> TypeRefKind.GENERIC | |
| "partial", "required", "readonly", "pick", "omit", "record" -> TypeRefKind.MAPPED | |
| else -> TypeRefKind.GENERIC | |
| } | |
| return CodeTypeRef( | |
| raw = raw, | |
| name = typeName, | |
| kind = kind, | |
| args = args, | |
| valueType = args.firstOrNull() // For Array<T>, store T as valueType | |
| ) | |
| // Special handling for common generic types | |
| val kind = when (typeName.lowercase()) { | |
| "array" -> TypeRefKind.ARRAY | |
| "promise", "observable" -> TypeRefKind.GENERIC | |
| "map", "set", "weakmap", "weakset" -> TypeRefKind.GENERIC | |
| "partial", "required", "readonly", "pick", "omit", "record" -> TypeRefKind.MAPPED | |
| else -> TypeRefKind.GENERIC | |
| } | |
| val arrayDims = if (kind == TypeRefKind.ARRAY) 1 else 0 | |
| return CodeTypeRef( | |
| raw = raw, | |
| name = typeName, | |
| kind = kind, | |
| args = args, | |
| valueType = args.firstOrNull(), // For Array<T>, store T as valueType | |
| arrayDimensions = arrayDims | |
| ) |
🤖 Prompt for AI Agents
In
`@chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.kt`
around lines 212 - 227, The CodeTypeRef returned for Array<T> currently sets
kind = TypeRefKind.ARRAY but leaves arrayDimensions at 0; update the return to
set arrayDimensions = 1 when kind == TypeRefKind.ARRAY (use the computed kind
variable) so that Array<T> and T[] are represented consistently; modify the
CodeTypeRef construction (fields raw, name/typeName, kind, args, valueType =
args.firstOrNull()) to also include arrayDimensions = if (kind ==
TypeRefKind.ARRAY) 1 else 0.
| // Parse parameter types | ||
| val paramTypes = ctx.parameterList()?.parameter()?.mapNotNull { param -> | ||
| param.requiredParameter()?.typeAnnotation()?.let { build(it) } | ||
| ?: param.optionalParameter()?.typeAnnotation()?.let { build(it) } | ||
| } ?: listOf() | ||
|
|
||
| // Parse return type | ||
| val returnType = buildFromType(ctx.type_()) | ||
|
|
||
| return CodeTypeRef( | ||
| raw = raw, | ||
| kind = TypeRefKind.FUNCTION, | ||
| parameterTypes = paramTypes, | ||
| returnType = returnType | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file structure and understand the context
find . -name "TypeScriptTypeRefBuilder.kt" -type f | head -5Repository: phodal/chapi
Length of output: 146
🏁 Script executed:
# Look at the specific lines in question and surrounding context
cat -n chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.kt | sed -n '240,285p'Repository: phodal/chapi
Length of output: 1578
🏁 Script executed:
# Check the TypeScript grammar file to understand parameterList structure
find . -name "*.g4" -type f | grep -i typescriptRepository: phodal/chapi
Length of output: 170
🏁 Script executed:
# Let's examine the TypeScript grammar to understand parameterList structure
grep -n "parameterList\|restParameter" chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4 | head -20Repository: phodal/chapi
Length of output: 524
🏁 Script executed:
# Get more context around the parameterList rule
sed -n '/^parameterList/,/^[a-z]/p' chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4 | head -30Repository: phodal/chapi
Length of output: 177
🏁 Script executed:
# Search for the parameterList rule definition more thoroughly
rg -A 10 "^\s*parameterList\s*:" chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's look at the restParameter rule definition
rg -A 5 "^restParameter" chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4Repository: phodal/chapi
Length of output: 219
🏁 Script executed:
# Let's check if there's any documentation or other uses of restParameter in the codebase
rg "restParameter" chapi-ast-typescript/src/main/kotlin/ --type kotlinRepository: phodal/chapi
Length of output: 969
🏁 Script executed:
# Also check the main parser grammar file more carefully to see how parameter() is defined
rg -A 3 "^parameter\s*:" chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's see the full parameter rule definition
rg -B 2 -A 15 "^parameter\s*:" chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's check what methods are available on the generated parser classes
# by looking at the imports and usage patterns in the existing code
rg "\.parameter\(\)|\.restParameter\(\)" chapi-ast-typescript/src/main/kotlin/ --type kotlin -B 2 -A 2 | head -40Repository: phodal/chapi
Length of output: 2979
🏁 Script executed:
# Let's see the complete context of how restParameter is used in TypeScriptAstListener
grep -B 3 -A 3 "restParameter()" chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptAstListener.kt | head -30Repository: phodal/chapi
Length of output: 658
🏁 Script executed:
# Let's examine the full implementation in TypeScriptAstListener to understand the pattern
sed -n '1,100p' chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptAstListener.kt | grep -A 20 "fun buildParameters"Repository: phodal/chapi
Length of output: 966
🏁 Script executed:
# Get more context on buildRestParameter method to understand what it returns
rg -B 2 -A 10 "fun buildRestParameter" chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptAstListener.ktRepository: phodal/chapi
Length of output: 541
🏁 Script executed:
# Check what type restParameter returns in the grammar and how typeAnnotation is used elsewhere
rg "typeAnnotation\(\)" chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.kt | head -10Repository: phodal/chapi
Length of output: 212
🏁 Script executed:
# Let's verify the exact structure of how parameter() returns in the generated parser
# by checking if there are any integration tests or examples that show function type handling
rg -l "buildFunctionType\|FunctionType" chapi-ast-typescript/src --type kotlinRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's also verify what the generated parser exposes for parameterList
# by examining usage patterns with restParameter in the codebase
rg "parameterList\(\)" chapi-ast-typescript/src/main/kotlin/ --type kotlin -A 2 | head -30Repository: phodal/chapi
Length of output: 1120
🏁 Script executed:
# Let's verify the complete grammar rules to ensure restParameter is indeed a direct child of parameterList
cat chapi-ast-typescript/src/main/antlr/TypeScriptParser.g4 | sed -n '206,235p'Repository: phodal/chapi
Length of output: 659
🏁 Script executed:
# Let's check if there are any tests that exercise function types with rest parameters
find . -name "*.kt" -type f -path "*/test/*" | xargs grep -l "restParameter\|function.*\\.\\.\\..*=>" | head -5Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's understand the actual impact by seeing how function types are being used
rg "buildFunctionType\|FunctionTypeContext" chapi-ast-typescript/src/main/kotlin/ --type kotlin -B 3 -A 3 | head -40Repository: phodal/chapi
Length of output: 38
Include rest parameter types in function type signatures.
The parameterList grammar rule allows restParameter as a direct child, either as the sole parameter or following regular parameters. The current code only processes parameter() elements, which omits rest parameters (...args: T[]). Add handling for ctx.parameterList()?.restParameter()?.typeAnnotation().
Reference: TypeScriptParser.g4 defines parameterList as:
parameterList
: restParameter
| parameter (',' parameter)* (',' restParameter)? ','?
🤖 Prompt for AI Agents
In
`@chapi-ast-typescript/src/main/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefBuilder.kt`
around lines 256 - 270, The current function building function type refs ignores
rest parameters because it only maps ctx.parameterList()?.parameter(); update
the logic in TypeScriptTypeRefBuilder (the block that computes paramTypes and
returnType and returns CodeTypeRef) to also check
ctx.parameterList()?.restParameter() for a typeAnnotation, call build(...) on
that typeAnnotation (same helper used for
parameter.requiredParameter()/optionalParameter()), and append that resulting
type to the parameterTypes list so rest parameters (both sole restParameter and
trailing restParameter) are included in the resulting CodeTypeRef; keep
returnType produced by buildFromType(ctx.type_()) unchanged.
Add TypeRefBuilder for Kotlin and C#: Kotlin: - Nullable types (T?) - Generic types (List<T>, Map<K,V>) - Function types ((A) -> B) - Pair/Triple as tuples C#: - Nullable reference types (T?) - Generic types (List<T>, Dictionary<K,V>) - Array types (T[], T[,]) - Pointer types (T*) - Tuple types ((T1, T2)) All existing tests pass.
Add TypeRefBuilder for Python and C++: Python: - Optional[T], Union[T1, T2] handling - List[T], Dict[K,V], Set[T], Tuple[T...] - Callable[[Args], Return] function types - Python 3.10+ union syntax (T | U) - Type annotations (PEP 484) C++: - Pointer types (T*) and references (T&, T&&) - Template types (vector<T>, map<K,V>) - Smart pointers (unique_ptr, shared_ptr) - std::optional, std::pair, std::tuple - const qualifiers All existing tests pass.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt`:
- Around line 81-88: The TypeRef construction in CPPBasicIdentListener (when
building CodeProperty for parameters and fields) only uses the first
typeSpecifier() and loses cv-qualifiers and pointer/reference ops; fix by
passing the full combined type text to the builder: concatenate
declSpecifierSeq().text and declarator().text (or otherwise use the full
declSpecifierSeq plus declarator) and call
CPPTypeRefBuilder.buildFromString(fullTypeString) instead of
CPPTypeRefBuilder.build(typeSpec); alternatively add/use a CPPTypeRefBuilder
overload that accepts both declSpecifierSeq and declarator contexts and
preserves cv-qualifiers and pointer/reference information when constructing the
TypeRef.
In
`@chapi-ast-csharp/src/main/kotlin/chapi/ast/csharpast/CSharpTypeRefBuilder.kt`:
- Around line 38-45: The code miscomputes multi-dimensional array dimensions and
mishandles brackets when parsing types: change the arrayDims calculation (where
arrayRanks, isArray, arrayDims are computed) to derive dimensions from each
rank_specifier by counting commas inside each rank_specifier and adding 1 (e.g.
dims += commas + 1 per rank_specifier) instead of using rank_specifier count;
update buildFromString() to recognize rank_specifier patterns like "[,]" or
"[,,]" (treat any "[...]" with n commas as an array with n+1 dimensions) in
addition to "[]" so multi-dimensional arrays become ARRAY kinds rather than
SIMPLE; and fix splitTypeList() to ignore commas inside square brackets (and
angle brackets) by tracking nesting depth for '<'/'>' and '['/']' so type
arguments like "Dictionary<int[,], string>" are not split on inner commas.
Ensure you reference/modify the functions/variables buildFromString,
splitTypeList, and the arrayRanks/arrayDims logic in CSharpTypeRefBuilder.
In
`@chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt`:
- Around line 78-100: The union parsing branch in PythonTypeRefBuilder (the
when-case that checks raw.contains("|") && !raw.contains("[")) wrongly blocks
generics and parentheses and uses naive raw.split("|"); remove the
!raw.contains("[") guard, add a depth-aware helper (e.g., splitUnionList similar
to splitTypeList) that respects brackets/parentheses and splits only top-level
'|' tokens, then replace raw.split("|").map { buildFromString(...) } with
splitUnionList(raw).map { buildFromString(it) } and keep the existing Optional
detection/CodeTypeRef construction (references: PythonTypeRefBuilder,
splitTypeList, splitUnionList helper, buildFromString, CodeTypeRef).
In `@chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt`:
- Around line 46-57: The traitObjectTypeOneBound branch currently falls back to
buildTypePath when raw doesn't start with "dyn ", causing bare trait objects to
be classified as SIMPLE; change the logic in RustTypeRefBuilder (the
traitObjectTypeOneBound handling) so that whenever ctx.traitObjectTypeOneBound()
is present you treat it as a trait object (INTERFACE) — e.g. always call
buildTraitObjectOneBound(traitObj, raw) (or otherwise ensure
buildTraitObjectOneBound is used even when raw lacks "dyn ") instead of
delegating to buildTypePath; update any related tests to assert that bare trait
objects like Box<MyTrait> produce INTERFACE TypeRefKind.
🧹 Nitpick comments (11)
chapi-ast-java/src/test/kotlin/chapi/ast/javaast/JavaTypeRefTest.kt (1)
259-278: Consider using safe-call operators for consistency.Lines 265, 275-277 access
typeRef.argsdirectly without null-safe operators (?.). While this is acceptable in tests (NPE would simply fail the test), usingtypeRef.args?.sizewould be consistent with assertions elsewhere in this file (e.g., lines 77-78, 98) and would provide clearer failure messages ifargsis unexpectedly null.♻️ Optional consistency improvement
`@Test` fun `should build generic type from string`() { val typeRef = JavaTypeRefBuilder.buildFromString("List<String>") assertEquals(TypeRefKind.GENERIC, typeRef.kind) assertEquals("List", typeRef.name) - assertEquals(1, typeRef.args.size) - assertEquals("String", typeRef.args.first().name) + assertEquals(1, typeRef.args?.size) + assertEquals("String", typeRef.args?.first()?.name) } `@Test` fun `should build nested generic type from string`() { val typeRef = JavaTypeRefBuilder.buildFromString("Map<String, List<Integer>>") assertEquals(TypeRefKind.GENERIC, typeRef.kind) assertEquals("Map", typeRef.name) - assertEquals(2, typeRef.args.size) - assertEquals("String", typeRef.args[0].name) - assertEquals("List", typeRef.args[1].name) + assertEquals(2, typeRef.args?.size) + assertEquals("String", typeRef.args?.get(0)?.name) + assertEquals("List", typeRef.args?.get(1)?.name) }chapi-ast-go/src/test/kotlin/chapi/ast/goast/GoTypeRefTest.kt (1)
240-254: Strengthen multiple-return assertions.
This test currently checks only count; asserting actual return types would prevent false positives.♻️ Suggested enhancement
val container = analyse(code) val func = container.DataStructures.flatMap { it.Functions }.first { it.Name == "divide" } assertEquals(2, func.MultipleReturns.size) + val firstReturn = func.MultipleReturns[0] + val secondReturn = func.MultipleReturns[1] + assertNotNull(firstReturn.TypeRef) + assertEquals("int", firstReturn.TypeRef?.name) + assertNotNull(secondReturn.TypeRef) + assertEquals("error", secondReturn.TypeRef?.name)chapi-ast-typescript/src/test/kotlin/chapi/ast/typescriptast/TypeScriptTypeRefTest.kt (5)
53-56: Minor inconsistency in field lookup patterns.Interface fields are found using
TypeValue == "name"(line 53), while class fields later useTypeKey == "name"(line 214). If this reflects actual implementation differences between how TypeScript interfaces vs classes store field names, consider adding a brief comment to clarify this for future maintainers.
110-115: Consider adding assertions for Map key/value types.The test verifies
args.sizeis 2 but doesn't assert the actual key and value types. Based on patterns in other language tests (Java, Rust), consider adding explicit checks:assertEquals("string", dataField.TypeRef?.args?.get(0)?.name) assertEquals("number", dataField.TypeRef?.args?.get(1)?.name)Alternatively, if
keyType/valueTypefields are populated (as in GoTypeRefTest), those could be asserted instead.
134-136: Assertions work but could be more idiomatic.The contains checks are functional but verbose. A slight simplification using
assertTruewith a clearer message:assertTrue(unionTypes.orEmpty().containsAll(listOf("string", "number")))Minor style preference - current approach is acceptable.
156-176: Consider adding multi-dimensional array test.The current test covers single-dimensional bracket notation. TypeScript supports multi-dimensional arrays (
string[][]) which would testarrayDimensions > 1. This would strengthen coverage for edge cases.
180-195: Consider adding heterogeneous tuple test.The current tuple test uses identical element types (
[number, number]), making line 194's assertion (all { it.name == "number" }) valid but not thoroughly testing element differentiation. A test with mixed types (e.g.,[string, number, boolean]) would verify correct element-by-element type extraction.chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt (1)
31-76: Normalizetyping.-qualified names for Optional/Union/etc.Line 31-76: patterns like
typing.Optional[int]ortyping.Union[...]won’t match the currentOptional[/Union[branches and will fall through to GENERIC/SIMPLE. Consider stripping common prefixes before pattern matching so fully-qualified typing names are handled consistently.♻️ Suggested normalization pattern
- val raw = typeStr.trim() + val raw = typeStr.trim() + val normalized = raw + .removePrefix("typing.") + .removePrefix("typing_extensions.") @@ - return when { - raw == "None" -> CodeTypeRef(raw = raw, name = "None", kind = TypeRefKind.SIMPLE) + return when { + normalized == "None" -> CodeTypeRef(raw = raw, name = "None", kind = TypeRefKind.SIMPLE) @@ - raw.startsWith("Optional[") && raw.endsWith("]") -> { - val innerRaw = raw.substring(9, raw.length - 1) + normalized.startsWith("Optional[") && normalized.endsWith("]") -> { + val innerRaw = normalized.substring(9, normalized.length - 1) val inner = buildFromString(innerRaw) CodeTypeRef( raw = raw,Apply the same
normalizedvariable toUnion[...],List[...],Dict[...], etc.chapi-ast-rust/src/test/kotlin/chapi/ast/rustast/RustTypeRefTest.kt (1)
211-280: Consider a bare trait-object test case.Line 211-280: the builder tests are solid, but there’s no coverage for
Box<Trait>vsBox<dyn Trait>. Adding a test for both would validate how bare trait objects are classified and align with the builder’straitObjectTypeOneBoundhandling.chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPTypeRefBuilder.kt (1)
203-226:&&branch is unreachable due to earlier&check.Line 203-226:
raw.endsWith("&")will also match&&, so the rvalue-reference branch never executes. If you want to distinguish later, reorder the checks or remove the dead branch for clarity.♻️ Suggested reordering
- // Reference type - raw.endsWith("&") -> { + // Rvalue reference + raw.endsWith("&&") -> { + val baseRaw = raw.dropLast(2).trim() + val base = buildFromString(baseRaw) + CodeTypeRef( + raw = raw, + name = base.name, + kind = TypeRefKind.REFERENCE, + isReference = true, + valueType = base + ) + } + // Reference type + raw.endsWith("&") -> { val baseRaw = raw.dropLast(1).trim() val base = buildFromString(baseRaw) CodeTypeRef( raw = raw, name = base.name, kind = TypeRefKind.REFERENCE, isReference = true, valueType = base ) } - // Rvalue reference - raw.endsWith("&&") -> { - val baseRaw = raw.dropLast(2).trim() - val base = buildFromString(baseRaw) - CodeTypeRef( - raw = raw, - name = base.name, - kind = TypeRefKind.REFERENCE, - isReference = true, - valueType = base - ) - }chapi-ast-kotlin/src/main/kotlin/chapi/ast/kotlinast/KotlinTypeRefBuilder.kt (1)
221-234: String parsing doesn’t mirror Kotlin collection/tuple mapping.Line 221-234:
buildFromStringmaps allName<Args>to GENERIC, while the AST path sets ARRAY/TUPLE/Map key/value metadata for common Kotlin types. This can yield inconsistent TypeRefKind depending on entry point. Consider reusing the same special-case mapping forArray,List,Set,Map,Pair,Triple, etc.♻️ Example alignment for common Kotlin types
- raw.contains("<") && raw.endsWith(">") -> { + raw.contains("<") && raw.endsWith(">") -> { val bracketStart = raw.indexOf('<') val name = raw.substring(0, bracketStart) val argsRaw = raw.substring(bracketStart + 1, raw.length - 1) val args = splitTypeList(argsRaw).map { buildFromString(it.trim()) } - - CodeTypeRef( - raw = raw, - name = name, - kind = TypeRefKind.GENERIC, - args = args - ) + when (name.lowercase()) { + "array" -> CodeTypeRef(raw = raw, name = name, kind = TypeRefKind.ARRAY, args = args, valueType = args.firstOrNull()) + "map", "mutablemap", "hashmap", "linkedhashmap" -> + CodeTypeRef(raw = raw, name = name, kind = TypeRefKind.GENERIC, args = args, keyType = args.getOrNull(0), valueType = args.getOrNull(1)) + "pair", "triple" -> CodeTypeRef(raw = raw, name = name, kind = TypeRefKind.TUPLE, args = args, tupleElements = args) + else -> CodeTypeRef(raw = raw, name = name, kind = TypeRefKind.GENERIC, args = args) + } }
| val typeSpec = it.declSpecifierSeq().declSpecifier().firstOrNull()?.typeSpecifier() | ||
| val type = typeSpec?.text | ||
| val name = it.declarator()?.text | ||
|
|
||
| CodeProperty(TypeValue = name ?: "", TypeType = type ?: "") | ||
| CodeProperty( | ||
| TypeValue = name ?: "", | ||
| TypeType = type ?: "", | ||
| TypeRef = CPPTypeRefBuilder.build(typeSpec) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and read the CPPBasicIdentListener.kt file
fd CPPBasicIdentListener.ktRepository: phodal/chapi
Length of output: 127
🏁 Script executed:
# Read the file to examine lines 81-88 and 117-128
cat -n chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt | sed -n '75,135p'Repository: phodal/chapi
Length of output: 2849
🏁 Script executed:
# Now let's find and examine the CPPTypeRefBuilder to understand how it processes typeSpec
fd CPPTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 123
🏁 Script executed:
# Read the CPPTypeRefBuilder to understand how it processes typeSpec
cat -n chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 14403
🏁 Script executed:
# Check if there are other overloads of build() method
rg "fun build" chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 521
🏁 Script executed:
# Search for all usages of CPPTypeRefBuilder to see if there are alternative patterns
rg "CPPTypeRefBuilder" chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/ -A 2Repository: phodal/chapi
Length of output: 937
🏁 Script executed:
# Let's check what declSpecifierSeq actually contains and how declarator is structured
# Look at the grammar context to understand what information is available
rg "declSpecifierSeq|declarator" chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/ -B 2 -A 2 | head -40Repository: phodal/chapi
Length of output: 3188
🏁 Script executed:
# Let's check how return type is handled to see the difference
cat -n chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt | sed -n '39,55p'Repository: phodal/chapi
Length of output: 767
🏁 Script executed:
# Check if there's anywhere that combines declSpecifierSeq + declarator for type construction
cat -n chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt | sed -n '50,70p'Repository: phodal/chapi
Length of output: 920
🏁 Script executed:
# Final confirmation: check if there's any special handling elsewhere for parameters
rg "buildParameters|memberDeclaratorList" chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/ -A 5 | head -50Repository: phodal/chapi
Length of output: 2279
TypeRef construction is incomplete for parameters and fields, losing cv-qualifiers and pointer/reference information.
Lines 81-88 (parameters) and 117-128 (fields) extract only the first typeSpecifier() from declSpecifierSeq() and pass it alone to CPPTypeRefBuilder.build(typeSpec). This discards:
- Other cv-qualifiers and specifiers in
declSpecifierSeq() - Pointer/reference operators (
*,&,&&) from thedeclarator()
As a result, TypeRef loses const, volatile, and pointer/reference information. For example, const std::string& becomes const or std::string, missing the reference and any qualifiers outside the first type specifier.
The builder has buildFromString() that handles these cases, but it's never called for parameters or fields. Consider either:
- Passing the full combined type string (
declSpecifierSeq().text + declarator().text) tobuildFromString(), or - Adding an overload to
CPPTypeRefBuilder.build()that accepts bothdeclSpecifierSeqanddeclaratorcontexts
🤖 Prompt for AI Agents
In `@chapi-ast-cpp/src/main/kotlin/chapi/ast/cppast/CPPBasicIdentListener.kt`
around lines 81 - 88, The TypeRef construction in CPPBasicIdentListener (when
building CodeProperty for parameters and fields) only uses the first
typeSpecifier() and loses cv-qualifiers and pointer/reference ops; fix by
passing the full combined type text to the builder: concatenate
declSpecifierSeq().text and declarator().text (or otherwise use the full
declSpecifierSeq plus declarator) and call
CPPTypeRefBuilder.buildFromString(fullTypeString) instead of
CPPTypeRefBuilder.build(typeSpec); alternatively add/use a CPPTypeRefBuilder
overload that accepts both declSpecifierSeq and declarator contexts and
preserves cv-qualifiers and pointer/reference information when constructing the
TypeRef.
| // Check for array rank specifiers | ||
| val arrayRanks = parentCtx.rank_specifier() | ||
| val isArray = arrayRanks?.isNotEmpty() == true | ||
| val arrayDims = arrayRanks?.size ?: 0 | ||
|
|
||
| // Check for pointer types (*) | ||
| val pointerDepth = parentCtx.STAR()?.size ?: 0 | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, locate the file and check its size
find . -name "CSharpTypeRefBuilder.kt" -type fRepository: phodal/chapi
Length of output: 134
🏁 Script executed:
#!/bin/bash
# Read the CSharpTypeRefBuilder.kt file to understand the full context
cat -n chapi-ast-csharp/src/main/kotlin/chapi/ast/csharpast/CSharpTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 13494
🏁 Script executed:
#!/bin/bash
# Also check if there's a C# grammar file in the project
find . -name "*.g4" -type f | head -20Repository: phodal/chapi
Length of output: 999
🏁 Script executed:
#!/bin/bash
# Look for test files related to CSharpTypeRef
find . -path "*/test*" -name "*CSharp*TypeRef*" -o -path "*/test*" -name "*csharp*" -type f | grep -i test | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
#!/bin/bash
# Find and examine the rank_specifier in CSharp grammar
rg -n "rank_specifier" chapi-ast-csharp/src/main/antlr/CSharpParser.g4 -A 3Repository: phodal/chapi
Length of output: 987
🏁 Script executed:
#!/bin/bash
# Look for test files
find . -path "*/test*" -name "*CSharp*" -type f | head -30Repository: phodal/chapi
Length of output: 221
🏁 Script executed:
#!/bin/bash
# Search for any existing tests related to array types
rg -i "array|rank|\\[" --type kotlin chapi-ast-csharp/src/test -l 2>/dev/null | head -10Repository: phodal/chapi
Length of output: 217
🏁 Script executed:
#!/bin/bash
# Check if there are examples of multi-dimensional arrays being parsed
rg "\\[,+\\]" . --type kotlin --type java -B 2 -A 2 2>/dev/null | head -50Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
#!/bin/bash
# Examine the test files
cat -n chapi-ast-csharp/src/test/kotlin/chapi/ast/csharpast/CSharpAstListenerTest.kt | head -100Repository: phodal/chapi
Length of output: 3300
🏁 Script executed:
#!/bin/bash
# Check for multi-dimensional array test cases
rg "int\[" chapi-ast-csharp/src/test -B 2 -A 2Repository: phodal/chapi
Length of output: 4481
🏁 Script executed:
#!/bin/bash
# Check if there are any test cases with generics containing arrays
rg "Dictionary|List" chapi-ast-csharp/src/test -B 1 -A 1Repository: phodal/chapi
Length of output: 14814
🏁 Script executed:
#!/bin/bash
# Check where buildFromString is used in the codebase
rg -n "buildFromString" chapi-ast-csharp/src --type kotlin -B 2 -A 2Repository: phodal/chapi
Length of output: 3174
🏁 Script executed:
#!/bin/bash
# Verify the exact behavior - see if there are other references to array handling
rg -n "arrayDimensions" chapi-ast-csharp/src --type kotlin -B 2 -A 2Repository: phodal/chapi
Length of output: 1235
🏁 Script executed:
#!/bin/bash
# Look for usages of the TYPE system to understand the impact
rg -n "CodeTypeRef" chapi-ast-csharp/src/main/kotlin --type kotlin -l | head -10Repository: phodal/chapi
Length of output: 132
🏁 Script executed:
#!/bin/bash
# Check for any test cases specifically testing array types
rg -n "\[\]|\[," chapi-ast-csharp/src/test/kotlin --type kotlin -B 2 -A 2Repository: phodal/chapi
Length of output: 2006
🏁 Script executed:
#!/bin/bash
# Look for the specific test cases mentioned in the grammar file
grep -n "int\[\|Dictionary" chapi-ast-csharp/src/test/resources/grammar/AllInOneNoPreprocessor.cs | head -15Repository: phodal/chapi
Length of output: 1038
🏁 Script executed:
#!/bin/bash
# Verify the exact syntax from grammar examples
grep -n "int\[,\|int\[\[" chapi-ast-csharp/src/test/resources/grammar/AllInOneNoPreprocessor.csRepository: phodal/chapi
Length of output: 156
Fix multi-dimensional array and bracket handling in type parsing.
The current implementation has three critical bugs:
-
Line 41:
arrayDims = arrayRanks?.sizecounts rank_specifier objects, not dimensions. According to the C# grammar (rank_specifier : '[' ','* ']'),int[, ,]has one rank_specifier with 2 commas representing 3 dimensions, but this code would set arrayDims=1. -
Lines 242-253:
buildFromString()only recognizes[]patterns and ignores multi-dimensional notation like[,]or[,,]. The test file containsint[, ,] cubewhich would fail to parse as an array type and incorrectly fall through to SIMPLE. -
Lines 299-318:
splitTypeList()does not track square brackets, soDictionary<int[,], string>will be split at the comma inside[,], producing incorrect type arguments.
Suggested fixes
- val arrayDims = arrayRanks?.size ?: 0
+ val arrayDims = arrayRanks?.sumOf { rank ->
+ rank.text.count { it == ',' } + 1
+ } ?: 0- raw.endsWith("[]") -> {
- val dims = raw.count { it == '[' }
- val baseRaw = raw.replace("[]", "")
- val base = buildFromString(baseRaw)
- CodeTypeRef(
- raw = raw,
- name = base.name,
- kind = TypeRefKind.ARRAY,
- valueType = base,
- arrayDimensions = dims
- )
- }
+ Regex("(\\[[,]*\\])+$").containsMatchIn(raw) -> {
+ val suffix = Regex("(\\[[,]*\\])+$").find(raw)!!.value
+ val baseRaw = raw.dropLast(suffix.length)
+ val dims = Regex("\\[[,]*\\]").findAll(suffix)
+ .sumOf { it.value.count { c -> c == ',' } + 1 }
+ val base = buildFromString(baseRaw)
+ CodeTypeRef(
+ raw = raw,
+ name = base.name,
+ kind = TypeRefKind.ARRAY,
+ valueType = base,
+ arrayDimensions = dims,
+ args = base.args
+ )
+ } '<', '(' -> {
depth++
current.append(char)
}
- '>', ')' -> {
+ '>', ')', ']' -> {
depth--
current.append(char)
}
+ '[' -> {
+ depth++
+ current.append(char)
+ }🤖 Prompt for AI Agents
In `@chapi-ast-csharp/src/main/kotlin/chapi/ast/csharpast/CSharpTypeRefBuilder.kt`
around lines 38 - 45, The code miscomputes multi-dimensional array dimensions
and mishandles brackets when parsing types: change the arrayDims calculation
(where arrayRanks, isArray, arrayDims are computed) to derive dimensions from
each rank_specifier by counting commas inside each rank_specifier and adding 1
(e.g. dims += commas + 1 per rank_specifier) instead of using rank_specifier
count; update buildFromString() to recognize rank_specifier patterns like "[,]"
or "[,,]" (treat any "[...]" with n commas as an array with n+1 dimensions) in
addition to "[]" so multi-dimensional arrays become ARRAY kinds rather than
SIMPLE; and fix splitTypeList() to ignore commas inside square brackets (and
angle brackets) by tracking nesting depth for '<'/'>' and '['/']' so type
arguments like "Dictionary<int[,], string>" are not split on inner commas.
Ensure you reference/modify the functions/variables buildFromString,
splitTypeList, and the arrayRanks/arrayDims logic in CSharpTypeRefBuilder.
| // T | U - Python 3.10+ union syntax | ||
| raw.contains("|") && !raw.contains("[") -> { | ||
| val elements = raw.split("|").map { buildFromString(it.trim()) } | ||
| val hasNone = elements.any { it.name == "None" } | ||
| val nonNoneElements = elements.filter { it.name != "None" } | ||
|
|
||
| if (hasNone && nonNoneElements.size == 1) { | ||
| CodeTypeRef( | ||
| raw = raw, | ||
| name = "Optional", | ||
| kind = TypeRefKind.NULLABLE, | ||
| nullable = true, | ||
| valueType = nonNoneElements.first(), | ||
| union = elements | ||
| ) | ||
| } else { | ||
| CodeTypeRef( | ||
| raw = raw, | ||
| kind = TypeRefKind.UNION, | ||
| union = elements | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
git ls-files "chapi-ast-python" | head -20Repository: phodal/chapi
Length of output: 168
🏁 Script executed:
find . -name "PythonTypeRefBuilder.kt" -type fRepository: phodal/chapi
Length of output: 168
🏁 Script executed:
cat -n chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt | sed -n '70,110p'Repository: phodal/chapi
Length of output: 2019
🏁 Script executed:
cat -n chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt | sed -n '1,50p'Repository: phodal/chapi
Length of output: 2005
🏁 Script executed:
cat -n chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt | sed -n '48,77p'Repository: phodal/chapi
Length of output: 1522
🏁 Script executed:
wc -l chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 136
🏁 Script executed:
rg -n "split.*\|" chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/ -A 3 -B 1Repository: phodal/chapi
Length of output: 751
🏁 Script executed:
find . -path "*test*" -name "*PythonTypeRefBuilder*" -o -path "*test*" -name "*python*" -type f | grep -i test | head -10Repository: phodal/chapi
Length of output: 123
🏁 Script executed:
cat -n chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt | sed -n '200,297p'Repository: phodal/chapi
Length of output: 3851
🏁 Script executed:
rg -n "fun splitTypeList" chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt -A 15Repository: phodal/chapi
Length of output: 605
🏁 Script executed:
find . -path "*test*" -name "*TypeRef*" -type f | head -10Repository: phodal/chapi
Length of output: 491
🏁 Script executed:
find . -path "*test*" -name "*PythonTypeRef*" -type fRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
rg -n "3\.10.*union\||.*\|.*None" chapi-ast-python/ -iRepository: phodal/chapi
Length of output: 186
🏁 Script executed:
cat -n chapi-ast-python/src/test/kotlin/chapi/ast/pythonast/ 2>/dev/null | head -50Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
find ./chapi-ast-python -path "*test*" -type f -name "*.kt"Repository: phodal/chapi
Length of output: 218
🏁 Script executed:
rg -n "PythonTypeRefBuilder\|buildFromString" chapi-ast-python/src/test/kotlin/chapi/ast/pythonast/ -A 3 -B 1Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
cat -n chapi-ast-python/src/test/kotlin/chapi/ast/pythonast/PythonAnalyserTest.kt | head -100Repository: phodal/chapi
Length of output: 4411
🏁 Script executed:
python3 << 'EOF'
# Test case: list[int] | None with naive split
raw = "list[int] | None"
naive_split = raw.split("|")
print(f"Input: {raw}")
print(f"Naive split: {naive_split}")
print(f"Issue: '[' is present, so !raw.contains('[') guard blocks this type annotation")
print()
# What should happen with depth-aware split
def split_union(s):
result = []
depth = 0
current = []
for char in s:
if char in '([':
depth += 1
current.append(char)
elif char in ')]':
depth -= 1
current.append(char)
elif char == '|' and depth == 0:
result.append(''.join(current).strip())
current = []
else:
current.append(char)
if current:
result.append(''.join(current).strip())
return result
print(f"Depth-aware split: {split_union(raw)}")
EOFRepository: phodal/chapi
Length of output: 142
| union parsing incorrectly blocks generic and parenthesized union types.
Lines 78–100: The guard raw.contains("|") && !raw.contains("[") prevents parsing valid Python 3.10+ union syntax with nested generics. For example, list[int] | None, dict[str, int] | None, and tuple[int, ...] | None are rejected. Additionally, naive raw.split("|") would incorrectly split pipes inside brackets (e.g., dict[str | int, float]).
Replace the guard and implement depth-aware splitting, following the existing splitTypeList() pattern already present in this file (lines 248–280):
Suggested fix
- // T | U - Python 3.10+ union syntax
- raw.contains("|") && !raw.contains("[") -> {
- val elements = raw.split("|").map { buildFromString(it.trim()) }
+ // T | U - Python 3.10+ union syntax
+ raw.contains("|") -> {
+ val elements = splitUnionList(raw).map { buildFromString(it.trim()) }
val hasNone = elements.any { it.name == "None" }
val nonNoneElements = elements.filter { it.name != "None" }Add the helper (mirrors splitTypeList):
private fun splitUnionList(str: String): List<String> {
val result = mutableListOf<String>()
var depth = 0
var current = StringBuilder()
for (char in str) {
when (char) {
'[', '(' -> { depth++; current.append(char) }
']', ')' -> { depth--; current.append(char) }
'|' -> {
if (depth == 0) {
result.add(current.toString().trim())
current = StringBuilder()
} else {
current.append(char)
}
}
else -> current.append(char)
}
}
if (current.isNotEmpty()) result.add(current.toString().trim())
return result
}🤖 Prompt for AI Agents
In `@chapi-ast-python/src/main/kotlin/chapi/ast/pythonast/PythonTypeRefBuilder.kt`
around lines 78 - 100, The union parsing branch in PythonTypeRefBuilder (the
when-case that checks raw.contains("|") && !raw.contains("[")) wrongly blocks
generics and parentheses and uses naive raw.split("|"); remove the
!raw.contains("[") guard, add a depth-aware helper (e.g., splitUnionList similar
to splitTypeList) that respects brackets/parentheses and splits only top-level
'|' tokens, then replace raw.split("|").map { buildFromString(...) } with
splitUnionList(raw).map { buildFromString(it) } and keep the existing Optional
detection/CodeTypeRef construction (references: PythonTypeRefBuilder,
splitTypeList, splitUnionList helper, buildFromString, CodeTypeRef).
| // TraitObjectTypeOneBound - this is often how simple types are parsed! | ||
| // 'dyn'? traitBound, where traitBound eventually contains typePath | ||
| ctx.traitObjectTypeOneBound()?.let { traitObj -> | ||
| // If it has explicit 'dyn' keyword, treat as trait object | ||
| if (raw.startsWith("dyn ")) { | ||
| return buildTraitObjectOneBound(traitObj, raw) | ||
| } | ||
| // Otherwise, extract the typePath from traitBound | ||
| val typePath = traitObj.traitBound()?.typePath() | ||
| if (typePath != null) { | ||
| return buildTypePath(typePath, raw) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and understand its location
find . -name "RustTypeRefBuilder.kt" -type fRepository: phodal/chapi
Length of output: 128
🏁 Script executed:
# Let's examine the context around lines 46-57 in the file
cat -n chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt | head -100Repository: phodal/chapi
Length of output: 4381
🏁 Script executed:
# Search for the buildTypePath and buildTraitObjectOneBound implementations
rg -n "fun buildTypePath|fun buildTraitObjectOneBound" chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 270
🏁 Script executed:
# Let's look at the complete file to understand the full context
wc -l chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 130
🏁 Script executed:
# Let's look at buildTraitObjectOneBound implementation
sed -n '323,360p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1525
🏁 Script executed:
# Let's look at buildTypePath implementation
sed -n '97,150p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 2197
🏁 Script executed:
# Search for tests related to trait objects
find . -path "*test*" -name "*RustTypeRef*" -o -path "*test*" -name "*Rust*Test*" | grep -i rustRepository: phodal/chapi
Length of output: 276
🏁 Script executed:
# Let's search for test files in the Rust AST module
find . -path "*test*" -type f -name "*.kt" | grep -i rust | head -20Repository: phodal/chapi
Length of output: 276
🏁 Script executed:
# Let's examine the RustTypeRefTest.kt file for trait object tests
cat -n ./chapi-ast-rust/src/test/kotlin/chapi/ast/rustast/RustTypeRefTest.ktRepository: phodal/chapi
Length of output: 12210
🏁 Script executed:
# Let's also check the TypeRefKind to understand the classification system
find . -name "TypeRefKind.kt" -o -name "CodeTypeRef.kt" | head -5Repository: phodal/chapi
Length of output: 119
🏁 Script executed:
# Search for how trait objects are tested
rg -A 5 -B 2 "traitObject|dyn " ./chapi-ast-rust/src/test/kotlin/chapi/ast/rustast/RustTypeRefTest.ktRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's check if there are any trait object related tests or usages in the codebase
rg -i "traitobject|trait\sobject" --type ktRepository: phodal/chapi
Length of output: 82
🏁 Script executed:
# Let's look at the Rust grammar file to understand how traitObjectTypeOneBound is defined
find . -name "*.g4" | head -5Repository: phodal/chapi
Length of output: 274
🏁 Script executed:
# Let's search for any existing comments or documentation about bare trait objects
rg -i "bare.*trait|dyn.*trait" --type ktRepository: phodal/chapi
Length of output: 82
🏁 Script executed:
# Search for trait object related comments or code
rg -i "traitobject|trait object" chapi-ast-rust/Repository: phodal/chapi
Length of output: 1809
🏁 Script executed:
# Find the Rust grammar file
find . -path "*rust*" -name "*.g4"Repository: phodal/chapi
Length of output: 146
🏁 Script executed:
# Look at the Rust grammar to understand traitObjectTypeOneBound
find . -path "*rust*" -name "*.g4" -exec cat {} \;Repository: phodal/chapi
Length of output: 32690
🏁 Script executed:
# Let's search for how bare trait objects are used in tests or examples
rg -B 3 -A 3 "traitObjectTypeOneBound|Trait>" chapi-ast-rust/src/test/Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's check the buildTraitObjectType to compare with buildTraitObjectOneBound
sed -n '316,332p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 596
🏁 Script executed:
# Let's look at the complete file structure to understand type classification better
sed -n '1,95p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt | tail -50Repository: phodal/chapi
Length of output: 1937
🏁 Script executed:
# Let's look at the buildTraitObjectType to see the difference
sed -n '310,325p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 672
🏁 Script executed:
# Search for any actual usage of bare trait objects in test code or examples
find . -path "*test*" -name "*.rs" -o -path "*example*" -name "*.rs" | head -10Repository: phodal/chapi
Length of output: 678
🏁 Script executed:
# Let's check if there are any Rust test files showing trait object usage
find . -type f -name "*.rs" | head -20Repository: phodal/chapi
Length of output: 1088
🏁 Script executed:
# Let's look at the buildTypePath return to see what kind it returns for trait types
sed -n '97,145p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1987
🏁 Script executed:
# Let's check if there are any test cases with bare trait objects (without dyn)
rg -B 2 -A 2 "Box<[A-Z]|trait\s+\w+:" chapi-ast-rust/src/test/resources/grammar/Repository: phodal/chapi
Length of output: 5627
🏁 Script executed:
# Let's create a simple test to verify the behavior
cat > /tmp/test_trait.rs << 'EOF'
struct Container {
callback: Box<Trait>,
handler: dyn Handler,
}
trait Trait {
fn execute(&self);
}
trait Handler {
fn handle(&self);
}
EOF
cat /tmp/test_trait.rsRepository: phodal/chapi
Length of output: 209
🏁 Script executed:
# Search for any discussion of bare trait objects in comments
rg -i "bare.*trait|trait.*object" chapi-ast-rust/src/main/kotlin/Repository: phodal/chapi
Length of output: 1541
🏁 Script executed:
# Let's search for actual usage patterns with Box and traits in the test resources
rg "Box<" chapi-ast-rust/src/test/resources/grammar/ -A 1 -B 1 | head -30Repository: phodal/chapi
Length of output: 2280
🏁 Script executed:
# Check the complete buildTypePath to see what all cases return SIMPLE
sed -n '97,180p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt | grep -A 1 "TypeRefKind.SIMPLE"Repository: phodal/chapi
Length of output: 197
🏁 Script executed:
# Let's verify how buildTypePath handles trait types by checking what it returns
sed -n '97,175p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 2985
🏁 Script executed:
# Search for real examples of bare trait objects in the test resources
rg "Box<[A-Z][a-zA-Z0-9]*" chapi-ast-rust/src/test/resources/grammar/intellijrust_test_allinone.rsRepository: phodal/chapi
Length of output: 318
🏁 Script executed:
# Let's trace through what happens with Box<A + Copy>
# First check if it matches traitObjectTypeOneBound
# The content inside Box<...> would be parsed as a type_
# Let's look at what the actual flow would be for the bare trait object case
# Let's read the full buildTypePath to see what it returns
sed -n '97,180p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt | tail -40Repository: phodal/chapi
Length of output: 1379
🏁 Script executed:
# Let's verify the exact behavior by understanding what buildGenericArgs does with trait bounds
sed -n '200,250p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1673
🏁 Script executed:
# Let's check if there's any handling of trait bounds within generic arguments
rg -A 10 "fun buildGenericArgs" chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 518
🏁 Script executed:
# Let's understand the exact type_ parsing flow for Box<A + Copy>
# by checking what happens when buildGenericArgs calls build() on the inner type
# Let's create a test to verify this behavior
# First, let's check if the test file has actual Box<Trait> without dyn examples
cat chapi-ast-rust/src/test/resources/grammar/intellijrust_test_allinone.rs | grep -A 2 "type Sum = Box"Repository: phodal/chapi
Length of output: 117
🏁 Script executed:
# Let's trace what happens with Box<A + Copy>
# Box is generic, so it calls buildGenericArgs which calls build() on each type argument
# The inner type A + Copy would match traitObjectTypeOneBound
# Let's check the exact line where it extracts typePath
sed -n '46,60p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 716
🏁 Script executed:
# Now let's verify the complete flow - what does buildTypePath return for a simple trait name?
# For "A", buildTypePath would return SIMPLE (since it has no generic args)
sed -n '156,175p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 639
🏁 Script executed:
# Let's verify the exact behavior with a complete trace
# When we have Box<A + Copy>, let's see what the raw parameter value would be
# Let's check buildGenericArgs to see what raw value is passed to build()
sed -n '253,275p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 886
🏁 Script executed:
# Let me verify the exact buildGenericArgs implementation to see what raw value is passed
sed -n '253,280p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1089
🏁 Script executed:
# Let's check if buildGenericArgs passes the original text or reconstructed text
rg -A 15 "fun buildGenericArgs" chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 556
🏁 Script executed:
# Let's verify by checking what happens in the actual grammar
# when Box<Trait> is parsed
# First, let's understand: when buildGenericArgs calls build(typeCtx),
# what is typeCtx.text for the Trait part?
# Let's look at a concrete scenario - if we have Box<MyTrait>:
# 1. buildTypePath gets called for Box
# 2. It finds genericArgs, calls buildGenericArgs
# 3. buildGenericArgs calls build(typeCtx) where typeCtx is the context for MyTrait
# 4. In build(), typeCtx.text would be "MyTrait"
# 5. It matches traitObjectTypeOneBound (since it's a single bound without +)
# 6. raw = "MyTrait", which doesn't start with "dyn "
# 7. Falls through to buildTypePath(typePath, "MyTrait")
# 8. Returns SIMPLE
# Let's verify that Box is indeed handled specially
sed -n '120,145p' chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.ktRepository: phodal/chapi
Length of output: 1066
🏁 Script executed:
# Let's check what the expected behavior should be per Rust documentation
# A bare trait object without dyn is deprecated but still valid in older Rust editions
# Let's check if there are any tests that validate this specific scenario
cat chapi-ast-rust/src/test/kotlin/chapi/ast/rustast/RustTypeRefTest.kt | grep -i "trait" | head -20Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's verify by checking if there are any existing tests that would catch this issue
# and understand the intended behavior
# Check for any test involving Box and traits
rg -B 2 -A 5 "Box.*Trait|traitObject" chapi-ast-rust/src/test/kotlin/Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Let's also check if there's any comment or documentation about this behavior
rg -i "bare.*trait|deprecated.*trait|trait.*object" chapi-ast-rust/src/main/kotlin/ -B 2 -A 2Repository: phodal/chapi
Length of output: 4439
Bare trait objects without the dyn keyword are misclassified as SIMPLE instead of INTERFACE.
Lines 46-57: The code falls through to buildTypePath when raw doesn't start with "dyn ", which returns TypeRefKind.SIMPLE. Since the grammar rule traitObjectTypeOneBound makes dyn optional ('dyn'? traitBound), bare trait objects like Box<MyTrait> will be classified as SIMPLE rather than INTERFACE. This affects deprecated Rust syntax patterns from earlier editions. Consider always treating matched traitObjectTypeOneBound contexts as INTERFACE regardless of the dyn keyword presence, or add a test case to validate the desired behavior for bare trait objects.
🤖 Prompt for AI Agents
In `@chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustTypeRefBuilder.kt`
around lines 46 - 57, The traitObjectTypeOneBound branch currently falls back to
buildTypePath when raw doesn't start with "dyn ", causing bare trait objects to
be classified as SIMPLE; change the logic in RustTypeRefBuilder (the
traitObjectTypeOneBound handling) so that whenever ctx.traitObjectTypeOneBound()
is present you treat it as a trait object (INTERFACE) — e.g. always call
buildTraitObjectOneBound(traitObj, raw) (or otherwise ensure
buildTraitObjectOneBound is used even when raw lacks "dyn ") instead of
delegating to buildTypePath; update any related tests to assert that bare trait
objects like Box<MyTrait> produce INTERFACE TypeRefKind.
Summary
Implements Issue #41 Part 4 - structured type system for cross-language type analysis.
Key Features
TypeReffields default tonull, not breaking existing codeNew
CodeTypeRefClassSupports cross-language type constructs:
Domain Class Updates
Test Plan
CodeTypeRefTestwith 25 tests covering all type kindsNext Steps
Individual language parsers can now be updated to populate
TypeReffields:Addresses #41
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.