feat(ast): add Swift 5/6 parser module with comprehensive syntax support#50
feat(ast): add Swift 5/6 parser module with comprehensive syntax support#50
Conversation
Update README to reflect recent parser adaptations and structured CodeCall fields, ensuring documentation aligns with current implementation.
Add new :chapi-ast-swift module based on grammars-v4 swift5 grammar with minimal analyser + listener, plus basic tests and README updates. Closes #49.
|
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. 📝 WalkthroughWalkthroughAdds a new Gradle module Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant SwiftAnalyser
participant CharStreams
participant Swift5Lexer
participant CommonTokenStream
participant Swift5Parser
participant SwiftFullIdentListener
participant CodeContainer
Client->>SwiftAnalyser: analysis(code, filePath)
SwiftAnalyser->>CharStreams: fromString(code)
CharStreams-->>Swift5Lexer: charStream
Swift5Lexer->>CommonTokenStream: produce tokens
CommonTokenStream->>Swift5Parser: tokenStream
Swift5Parser->>SwiftFullIdentListener: parse tree (top_level)
SwiftFullIdentListener->>SwiftFullIdentListener: walk and collect nodes
SwiftFullIdentListener-->>SwiftAnalyser: CodeContainer
SwiftAnalyser-->>Client: CodeContainer
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 pull request adds comprehensive Swift 5 language support to the Chapi project by introducing a new :chapi-ast-swift module. The implementation provides basic parsing capabilities for Swift source code, including import declarations, type definitions (class, struct, enum), and function declarations.
Changes:
- New Swift AST parser module with ANTLR-based Swift 5 grammar implementation
- Integration of the module into the build system and test coverage aggregation
- Documentation updates reflecting Swift as a newly supported tier-3 language
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Adds :chapi-ast-swift module to tier 3 languages |
| build.gradle.kts | Includes Swift module in jacoco test coverage aggregation |
| chapi-ast-swift/build.gradle.kts | Build configuration for Swift module with ANTLR grammar generation |
| chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftAnalyser.kt | Main analyzer implementing the Analyser interface for Swift code parsing |
| chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt | ANTLR listener for extracting imports, types, and functions from Swift AST |
| chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | Parser support utilities for Swift operator handling and statement separation |
| chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupportLexer.java | Lexer support class with parenthesis tracking for Swift string interpolation |
| chapi-ast-swift/src/main/antlr/Swift5Parser.g4 | Swift 5 parser grammar with comprehensive language constructs |
| chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | Swift 5 lexer grammar with token definitions and string literal modes |
| chapi-ast-swift/src/test/kotlin/chapi/ast/swiftast/SwiftAnalyserTest.kt | Basic test coverage for imports, types, and functions parsing |
| README.md | Updates language coverage table and module list to include Swift; also updates other language status and version numbers |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | HTTP API decl | ✅ | 🆕 | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | 🆕 | ✅ | | | ||
| | Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🆕 | | ||
| | Function calls | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | ||
| | Arch/package | ✅ | | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | ✅ | ✅ | | |
There was a problem hiding this comment.
The README table update includes changes to existing languages (Go, C#, Scala) that are not mentioned in the PR description. The PR is focused on adding Swift support, but the table shows:
- Go: Changed from 🆕 to ✅ for "HTTP API decl" and "Function calls"
- C#: Changed from blank/🆕 to ✅ for "HTTP API decl", "Syntax parsing", "Function calls", and "Arch/package"
- Scala: Changed from blank to ✅ for "HTTP API decl" and "Syntax parsing"
These changes should either be documented in the PR description or separated into a different PR to maintain clear change tracking.
| | HTTP API decl | ✅ | 🆕 | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | 🆕 | ✅ | | | |
| | Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🆕 | | |
| | Function calls | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | |
| | Arch/package | ✅ | | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | ✅ | ✅ | | | |
| | HTTP API decl | ✅ | 🆕 | 🆕 | ✅ | ✅ | 🆕 | | | 🆕 | ✅ | | | |
| | Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | 🆕 | | |
| | Function calls | ✅ | ✅ | 🆕 | ✅ | ✅ | ✅ | | | | ✅ | | | |
| | Arch/package | ✅ | | ✅ | ✅ | ✅ | 🆕 | | | ✅ | ✅ | | |
| implementation "com.phodal.chapi:chapi-ast-java:2.5.2" | ||
| implementation "com.phodal.chapi:chapi-domain:2.5.2" |
There was a problem hiding this comment.
The version numbers are being updated from "2.3.6" to "2.5.2" in the usage example. This change is not related to adding Swift support and should either be documented in the PR description or handled separately. Version updates in documentation examples should be kept in sync with actual releases.
| implementation "com.phodal.chapi:chapi-ast-java:2.5.2" | |
| implementation "com.phodal.chapi:chapi-domain:2.5.2" | |
| implementation "com.phodal.chapi:chapi-ast-java:2.3.6" | |
| implementation "com.phodal.chapi:chapi-domain:2.3.6" |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #50 +/- ##
============================================
+ Coverage 67.64% 67.74% +0.09%
- Complexity 1433 1581 +148
============================================
Files 78 83 +5
Lines 6636 7351 +715
Branches 1262 1442 +180
============================================
+ Hits 4489 4980 +491
- Misses 1401 1479 +78
- Partials 746 892 +146 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Introduce SwiftTypeRefBuilder to enhance type reference handling in Swift AST. Update SwiftAnalyserTest and SwiftFullIdentListener to integrate the new builder, improving parser accuracy and maintainability.
- Add new Swift 6 keywords: async, await, actor, distributed, consuming, borrowing, nonisolated, isolated, sending, macro - Support typed throws syntax: throws(ErrorType) - Support async functions - Support actor and distributed actor declarations - Support ownership modifiers (consuming, borrowing) for parameters - Support noncopyable types with ~Copyable syntax - Add macro declaration support (#externalMacro) - Update README with Swift language coverage All 25 tests passing.
|
augment review |
🤖 Augment PR SummarySummary: Adds first-class Swift parsing support (Swift 5 + Swift 6 syntax) to Chapi via a new Gradle module. Changes:
Technical Notes: The Swift grammar includes Swift 6 constructs (typed throws, 🤖 Was this summary useful? React with 👍 or 👎 |
|
|
||
| int start = tokens.index(); | ||
| Token currentToken = tokens.get(start); | ||
| Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens |
There was a problem hiding this comment.
isBinaryOp (and similarly isPrefixOp/isPostfixOp) reads tokens.get(start - 1), which can throw if the operator is at the beginning of the token stream (start == 0). That would crash parsing on inputs that begin with an operator or when a predicate is evaluated at the first token.
🤖 Was this useful? React with 👍 or 👎
| val trimmed = typeText.trim() | ||
|
|
||
| // Handle optional types (suffix ? or !) | ||
| if (trimmed.endsWith("?") && !trimmed.startsWith("(")) { |
There was a problem hiding this comment.
| val postfixExpr = ctx.parent as? Swift5Parser.Postfix_expressionContext ?: return | ||
| val primaryExpr = postfixExpr.primary_expression() | ||
|
|
||
| val functionName = primaryExpr?.unqualified_name()?.identifier()?.text ?: primaryExpr?.text ?: return |
There was a problem hiding this comment.
In enterFunction_call_suffix, deriving functionName from postfixExpr.primary_expression() means member calls like obj.method() will likely be recorded as obj (the receiver) rather than method (the callee). That can make CodeFunction.FunctionCalls inaccurate for real-world Swift code.
🤖 Was this useful? React with 👍 or 👎
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@chapi-ast-swift/src/main/antlr/Swift5Parser.g4`:
- Around line 1470-1472: The function_type rule currently only allows an
optional plain THROWS token and must be updated to accept the full throw_clause
(including typed throws like throws(ErrorType)); modify the rule for
function_type to use throw_clause instead of THROWS? so it matches the pattern
used by function_signature and the Swift 6 grammar; look for the function_type
production and replace the THROWS? usage with the existing throw_clause symbol
so function type annotations can parse typed throws.
In
`@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt`:
- Around line 634-662: The enterFunction_call_suffix implementation incorrectly
uses the primary expression as the function name (so member calls like foo.bar()
record "foo"); update it to check the postfix expression chain for an
explicit_member_suffix and, if present, extract the identifier from the last
explicit_member_suffix instead of primary_expression. In other words, in
enterFunction_call_suffix (Swift5Parser.Function_call_suffixContext) inspect
postfixExpr (Postfix_expressionContext) for explicit_member_suffix nodes, pick
the last explicit_member_suffix().identifier()?.text as the functionName when
available, falling back to the existing primaryExpr-based extraction only if no
explicit_member_suffix is present; keep the rest of the call/argument
construction and adding to currentFunction unchanged.
- Around line 161-176: Replace the brittle substring parsing in
extractClassModifiers (SwiftFullIdentListener.extractClassModifiers) with
parse-tree token checks: use the
Swift5Parser.Class_declarationContext.access_level_modifier() and FINAL() calls
(and reuse the existing buildAccessLevelModifier(access_level_modifier())
helper) instead of inspecting ctx.text; specifically, if
ctx.access_level_modifier() is non-null add
buildAccessLevelModifier(ctx.access_level_modifier()) to modifiers, and if
ctx.FINAL() is present add "final" (avoiding substring matching so "fileprivate"
won't double-count as "private"); remove the current substring-based checks on
ctx.text.
In `@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt`:
- Around line 100-209: The protocol-composition handler in SwiftTypeRefBuilder
currently uses trimmed.split("&") which splits inside generics; replace that
with a depth-aware top-level ampersand splitter (follow the same approach as
splitByTopLevelComma) so "Foo<Bar & Baz> & Qux" yields two parts; implement a
helper (e.g., splitByTopLevelAmpersand) or generalize splitByTopLevelComma to
accept a delimiter, use it in the block that builds intersection types (the code
that maps parts to parseType and returns CodeTypeRef.intersection), and keep the
subsequent parseType mapping and return behavior unchanged.
- Around line 33-164: The optional-handling branches (the checks using
trimmed.endsWith("?") and trimmed.endsWith("!") in
SwiftTypeRefBuilder.parseType) incorrectly skip parenthesized types due to the
"!trimmed.startsWith("(")" guards; remove those "!trimmed.startsWith("(")"
conditions so parenthesized optionals like "(Int, String)?" and "((Int)->Void)?"
are parsed as nullable. Keep the tuple handler's existing check (startsWith("(")
&& endsWith(")") && !trimmed.contains("->")) and the function-type detection
that uses findTopLevelArrow so function types inside parentheses are still
recognized; ensure parseType continues to call CodeTypeRef.nullable(...) or
copy(nullable=true) for "?" and "!" respectively after removing the parentheses
guard.
Fix operator range, add bounds checks, and refine type parsing. Handle parenthesized optionals, improve array/dictionary detection, and fix protocol composition parsing. Use parse-tree tokens for modifiers and extract correct function names in member calls.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@chapi-ast-swift/src/main/antlr/Swift5Parser.g4`:
- Around line 985-987: The grammar omits the PACKAGE access level: update the
access_level_modifier rule (currently listing PRIVATE | FILE_PRIVATE | INTERNAL
| PUBLIC | OPEN) to include PACKAGE alongside those tokens, and also add PACKAGE
to the contextual keyword list in the identifier rule where access modifiers are
enumerated (the same list that includes
PRIVATE/FILE_PRIVATE/INTERNAL/PUBLIC/OPEN) so Swift 5.9+ `package` access
declarations are recognized; modify the rules referenced by name:
access_level_modifier and identifier to include the PACKAGE token.
In `@chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java`:
- Around line 134-152: The leftWS token set (leftWS) is missing comment tokens
which causes operators after comments to be misclassified; add
Swift5Parser.Line_comment and Swift5Parser.Block_comment to leftWS (the same
tokens present in rightWS) so functions that rely on leftWS like
isLeftOperatorWS correctly treat comments as whitespace when classifying
binary/left operators.
In
`@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt`:
- Around line 16-421: currentFunction is overwritten for nested
functions/initializers; preserve outer functions by replacing the single
currentFunction field with a stack: in enterFunction_declaration (and the
corresponding initializer handlers like enterInitializer_declaration) push the
existing currentFunction onto a new stack, set currentFunction to the new
CodeFunction, and on exitFunction_declaration (and exitInitializer_declaration)
pop the stack and restore the previous currentFunction (or set to null if stack
empty); update all places that assign/null currentFunction to use push/pop so
nested declarations restore outer contexts correctly.
- Around line 567-596: The protocol method visitor
enterProtocol_method_declaration is not capturing async/throws modifiers; update
it to mirror the logic used in enterFunction_declaration by inspecting
ctx.function_signature() for async_clause and throw_clause/throwing_specifier
(or the exact names used in your grammar), then set the corresponding fields on
the CodeFunction instance (e.g., function.Async = true and set function.Throws /
function.ReThrows or whatever properties your CodeFunction uses for throwing
modifiers). Ensure you pull these from ctx.function_signature()?.async_clause()
and ctx.function_signature()?.throw_clause() (or throwing_specifier) before
adding the function to the parent, so protocol requirements include async/throws
the same way as regular functions.
- Around line 599-627: In enterProtocol_property_declaration in
SwiftFullIdentListener, the modifiers for protocol properties omit the required
"var" token; update the logic that builds the modifiers (using
variable_declaration_head / buildModifiersFromDeclarationModifiers) to ensure
"var" is added to field.Modifiers (similar to how variable_declaration adds
"var" and constant_declaration adds "let"), e.g., append or include "var" to the
Modifiers list on the created CodeField before handling
getter_setter_keyword_block and calling addFieldToCurrentScope.
| access_level_modifier | ||
| : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (LPAREN SET RPAREN)? | ||
| ; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.g4" | head -20Repository: phodal/chapi
Length of output: 999
🏁 Script executed:
find . -type f -name "*Swift*" | grep -E "\.g4$"Repository: phodal/chapi
Length of output: 152
🏁 Script executed:
wc -l ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 109
🏁 Script executed:
sed -n '980,995p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 292
🏁 Script executed:
sed -n '1535,1550p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 297
🏁 Script executed:
sed -n '1540,1650p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 2086
🏁 Script executed:
wc -l ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4Repository: phodal/chapi
Length of output: 107
🏁 Script executed:
grep -n "PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4Repository: phodal/chapi
Length of output: 91
🏁 Script executed:
grep -n "PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
cat ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | head -110 | tail -20Repository: phodal/chapi
Length of output: 612
🏁 Script executed:
grep -n "| PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
web_search
"Swift 5.9 package access level"Repository: phodal/chapi
Length of output: 175
🌐 Web query:
Swift 5.9 package access level modifier
💡 Result:
Swift 5.9 adds a new access level: package.
- Meaning: package makes a declaration visible to any module/target that belongs to the same Swift package, but hides it from code outside that package (i.e., between internal and public). [1][2]
- Where it can be used: types, funcs, vars, protocols, etc., just like other access modifiers. [2]
- Key rules: a public API cannot expose a package type in its signature; a public class may not inherit from a package class; package members are usable across targets in the same package but not by importing packages. [2]
- Build/tooling: SwiftPM and modern Xcode pass package identity to the compiler so package-scoped access works; SwiftPM enables it for packages using Swift 5.9 by default. You can also enable upcoming features via swiftSettings if needed. [1][3]
References:
- Swift 5.9 release notes (swift.org). [1]
- SE-0386 / Swift Forums discussion: "New access modifier: package". [2]
- Swift 5.9 coverage in community docs / SwiftPM notes. [3]
🏁 Script executed:
grep -n "FILE_PRIVATE\|PRIVATE\|PUBLIC\|OPEN" ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 280
🏁 Script executed:
sed -n '1612,1620p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 203
🏁 Script executed:
sed -n '1600,1625p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4Repository: phodal/chapi
Length of output: 477
Add missing package access level support (Swift 5.9+).
The PACKAGE token exists in the lexer but is not used in the access_level_modifier rule (line 986) or the contextual keyword list in the identifier rule (lines 1612–1614). This prevents parsing of valid package access declarations introduced in Swift 5.9. Add PACKAGE to both locations.
Proposed fix
access_level_modifier
- : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (LPAREN SET RPAREN)?
+ : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN | PACKAGE) (LPAREN SET RPAREN)?
;Also add | PACKAGE to the identifier rule's contextual keyword list (around line 1614 where other access modifiers are listed).
📝 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.
| access_level_modifier | |
| : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (LPAREN SET RPAREN)? | |
| ; | |
| access_level_modifier | |
| : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN | PACKAGE) (LPAREN SET RPAREN)? | |
| ; |
🤖 Prompt for AI Agents
In `@chapi-ast-swift/src/main/antlr/Swift5Parser.g4` around lines 985 - 987, The
grammar omits the PACKAGE access level: update the access_level_modifier rule
(currently listing PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) to include
PACKAGE alongside those tokens, and also add PACKAGE to the contextual keyword
list in the identifier rule where access modifiers are enumerated (the same list
that includes PRIVATE/FILE_PRIVATE/INTERNAL/PUBLIC/OPEN) so Swift 5.9+ `package`
access declarations are recognized; modify the rules referenced by name:
access_level_modifier and identifier to include the PACKAGE token.
| leftWS.set(Swift5Parser.WS); | ||
| leftWS.set(Swift5Parser.LPAREN); | ||
| leftWS.set(Swift5Parser.Interpolation_multi_line); | ||
| leftWS.set(Swift5Parser.Interpolation_single_line); | ||
| leftWS.set(Swift5Parser.LBRACK); | ||
| leftWS.set(Swift5Parser.LCURLY); | ||
| leftWS.set(Swift5Parser.COMMA); | ||
| leftWS.set(Swift5Parser.COLON); | ||
| leftWS.set(Swift5Parser.SEMI); | ||
|
|
||
| rightWS.set(Swift5Parser.WS); | ||
| rightWS.set(Swift5Parser.RPAREN); | ||
| rightWS.set(Swift5Parser.RBRACK); | ||
| rightWS.set(Swift5Parser.RCURLY); | ||
| rightWS.set(Swift5Parser.COMMA); | ||
| rightWS.set(Swift5Parser.COLON); | ||
| rightWS.set(Swift5Parser.SEMI); | ||
| rightWS.set(Swift5Parser.Line_comment); | ||
| rightWS.set(Swift5Parser.Block_comment); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the SwiftSupport.java file
find . -name "SwiftSupport.java" -type fRepository: phodal/chapi
Length of output: 168
🏁 Script executed:
# Let's read the relevant section and surrounding context
head -160 chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | tail -40Repository: phodal/chapi
Length of output: 1713
🏁 Script executed:
# Let's see the full method that contains this code
grep -n "leftWS" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | head -5Repository: phodal/chapi
Length of output: 328
🏁 Script executed:
# Let's understand where these sets are used
grep -n "leftWS\|rightWS" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 1146
🏁 Script executed:
# Read the methods that use leftWS and rightWS
sed -n '335,345p' chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 424
🏁 Script executed:
# Get the full method names and context
sed -n '330,360p' chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 1383
🏁 Script executed:
# Search for where these methods are called
grep -n "isLeftWS\|isRightWS" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Look for operator-related methods that might use these checks
grep -n "isOperator\|operator" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | head -20Repository: phodal/chapi
Length of output: 1047
🏁 Script executed:
# Get broader context around the isLeftWS and isRightWS methods
sed -n '330,400p' chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 2081
🏁 Script executed:
# Search for where isLeftOperatorWS is called
rg "isLeftOperatorWS" --type javaRepository: phodal/chapi
Length of output: 533
🏁 Script executed:
# Search for where isRightOperatorWS is called
rg "isRightOperatorWS" --type javaRepository: phodal/chapi
Length of output: 537
🏁 Script executed:
# Look for Swift5Parser to understand token channels
find . -name "Swift5Parser.java" -o -name "*.g4" | grep -i swiftRepository: phodal/chapi
Length of output: 152
🏁 Script executed:
# Check if there's a Swift grammar file
find . -name "*.g4" -type fRepository: phodal/chapi
Length of output: 1439
🏁 Script executed:
# Search for HIDDEN_CHANNEL or channel assignments in the codebase
rg "HIDDEN_CHANNEL|channel.*comment|Line_comment|Block_comment" --type java | head -30Repository: phodal/chapi
Length of output: 443
🏁 Script executed:
# Check the Swift lexer grammar for comment channels
cat chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | grep -A 2 -B 2 "Line_comment\|Block_comment"Repository: phodal/chapi
Length of output: 304
🏁 Script executed:
# Look for channel directives in the Swift lexer
grep -n "channel\|skip\|Mode" chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | head -40Repository: phodal/chapi
Length of output: 978
🏁 Script executed:
# Get the full context around comment definitions
sed -n '1,100p' chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | grep -A 5 -B 5 "comment"Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Search for the context where isLeftOperatorWS and isRightOperatorWS are called
grep -B 10 -A 10 "boolean prevIsWS = isLeftOperatorWS" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | head -50Repository: phodal/chapi
Length of output: 2197
🏁 Script executed:
# Get more context around those usages
sed -n '250,320p' chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 3335
🏁 Script executed:
# Look for any handling of HIDDEN_CHANNEL tokens in token stream processing
rg "getHiddenTokensToLeft|getHiddenTokensToRight|HIDDEN_CHANNEL" chapi-ast-swift/src/main/java/chapi/ast/antlr/ -A 2 -B 2Repository: phodal/chapi
Length of output: 620
🏁 Script executed:
# Check what prevToken and nextToken are and how they're obtained
grep -B 20 "isLeftOperatorWS\|isRightOperatorWS" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java | grep -E "prevToken|nextToken|Token|get\(" | head -30Repository: phodal/chapi
Length of output: 1572
🏁 Script executed:
# Check the fillUp method and any token filtering before operator classification
sed -n '375,390p' chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 243
🏁 Script executed:
# Look for any filtering of HIDDEN_CHANNEL tokens that might skip comments before reaching operator checks
grep -n "HIDDEN_CHANNEL\|getHiddenTokens" chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.javaRepository: phodal/chapi
Length of output: 158
🏁 Script executed:
# Check if there's any preprocessing that creates a filtered token stream
rg "TokenStream|FilteringTokenStream|CommonTokenStream" chapi-ast-swift/src/main/java/chapi/ast/antlr/ -B 2 -A 2Repository: phodal/chapi
Length of output: 4520
Add comment tokens to leftWS for consistent operator classification.
leftWS omits Line_comment and Block_comment while rightWS includes them. Since comments occupy HIDDEN_CHANNEL but remain in the token stream, operators preceded by comments (e.g., a /*comment*/ + b) are misclassified. The + would incorrectly fail binary operator checks because isLeftOperatorWS(commentToken) returns false.
Fix
leftWS.set(Swift5Parser.SEMI);
+ leftWS.set(Swift5Parser.Line_comment);
+ leftWS.set(Swift5Parser.Block_comment);📝 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.
| leftWS.set(Swift5Parser.WS); | |
| leftWS.set(Swift5Parser.LPAREN); | |
| leftWS.set(Swift5Parser.Interpolation_multi_line); | |
| leftWS.set(Swift5Parser.Interpolation_single_line); | |
| leftWS.set(Swift5Parser.LBRACK); | |
| leftWS.set(Swift5Parser.LCURLY); | |
| leftWS.set(Swift5Parser.COMMA); | |
| leftWS.set(Swift5Parser.COLON); | |
| leftWS.set(Swift5Parser.SEMI); | |
| rightWS.set(Swift5Parser.WS); | |
| rightWS.set(Swift5Parser.RPAREN); | |
| rightWS.set(Swift5Parser.RBRACK); | |
| rightWS.set(Swift5Parser.RCURLY); | |
| rightWS.set(Swift5Parser.COMMA); | |
| rightWS.set(Swift5Parser.COLON); | |
| rightWS.set(Swift5Parser.SEMI); | |
| rightWS.set(Swift5Parser.Line_comment); | |
| rightWS.set(Swift5Parser.Block_comment); | |
| leftWS.set(Swift5Parser.WS); | |
| leftWS.set(Swift5Parser.LPAREN); | |
| leftWS.set(Swift5Parser.Interpolation_multi_line); | |
| leftWS.set(Swift5Parser.Interpolation_single_line); | |
| leftWS.set(Swift5Parser.LBRACK); | |
| leftWS.set(Swift5Parser.LCURLY); | |
| leftWS.set(Swift5Parser.COMMA); | |
| leftWS.set(Swift5Parser.COLON); | |
| leftWS.set(Swift5Parser.SEMI); | |
| leftWS.set(Swift5Parser.Line_comment); | |
| leftWS.set(Swift5Parser.Block_comment); | |
| rightWS.set(Swift5Parser.WS); | |
| rightWS.set(Swift5Parser.RPAREN); | |
| rightWS.set(Swift5Parser.RBRACK); | |
| rightWS.set(Swift5Parser.RCURLY); | |
| rightWS.set(Swift5Parser.COMMA); | |
| rightWS.set(Swift5Parser.COLON); | |
| rightWS.set(Swift5Parser.SEMI); | |
| rightWS.set(Swift5Parser.Line_comment); | |
| rightWS.set(Swift5Parser.Block_comment); |
🤖 Prompt for AI Agents
In `@chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java` around lines
134 - 152, The leftWS token set (leftWS) is missing comment tokens which causes
operators after comments to be misclassified; add Swift5Parser.Line_comment and
Swift5Parser.Block_comment to leftWS (the same tokens present in rightWS) so
functions that rely on leftWS like isLeftOperatorWS correctly treat comments as
whitespace when classifying binary/left operators.
| private var currentFunction: CodeFunction? = null | ||
|
|
||
| // ==================== Import Declaration ==================== | ||
|
|
||
| override fun enterImport_declaration(ctx: Swift5Parser.Import_declarationContext) { | ||
| val source = ctx.import_path()?.text ?: return | ||
| val kind = ctx.import_kind()?.text | ||
|
|
||
| val codeImport = CodeImport( | ||
| Source = source, | ||
| UsageName = listOf(source.split(".").last()), | ||
| PathSegments = source.split(".") | ||
| ) | ||
|
|
||
| // If import kind is specified (class, struct, enum, etc.) | ||
| if (kind != null) { | ||
| codeImport.Scope = kind | ||
| } | ||
|
|
||
| imports += codeImport | ||
| } | ||
|
|
||
| // ==================== Attribute and Modifier Helpers ==================== | ||
|
|
||
| private fun buildAnnotationsFromAttributes(ctx: Swift5Parser.AttributesContext?): List<CodeAnnotation> { | ||
| return ctx?.attribute()?.map { buildAnnotation(it) } ?: listOf() | ||
| } | ||
|
|
||
| private fun buildAnnotation(ctx: Swift5Parser.AttributeContext): CodeAnnotation { | ||
| val name = ctx.attribute_name()?.text ?: "" | ||
| val keyValues = mutableListOf<AnnotationKeyValue>() | ||
|
|
||
| // Parse attribute arguments if present | ||
| ctx.attribute_argument_clause()?.balanced_tokens()?.balanced_token()?.forEach { token -> | ||
| keyValues.add(AnnotationKeyValue(Key = token.text, Value = "")) | ||
| } | ||
|
|
||
| return CodeAnnotation( | ||
| Name = name, | ||
| KeyValues = keyValues, | ||
| Position = ctx.toPosition() | ||
| ) | ||
| } | ||
|
|
||
| private fun buildAccessLevelModifier(ctx: Swift5Parser.Access_level_modifierContext?): String { | ||
| if (ctx == null) return "" | ||
|
|
||
| val base = when { | ||
| ctx.PRIVATE() != null -> "private" | ||
| ctx.FILE_PRIVATE() != null -> "fileprivate" | ||
| ctx.INTERNAL() != null -> "internal" | ||
| ctx.PUBLIC() != null -> "public" | ||
| ctx.OPEN() != null -> "open" | ||
| else -> "" | ||
| } | ||
|
|
||
| // Check for (set) suffix | ||
| return if (ctx.SET() != null) { | ||
| "$base(set)" | ||
| } else { | ||
| base | ||
| } | ||
| } | ||
|
|
||
| private fun buildModifiersFromDeclarationModifiers(ctx: Swift5Parser.Declaration_modifiersContext?): List<String> { | ||
| return ctx?.declaration_modifier()?.mapNotNull { modCtx -> | ||
| // Check for access level modifier | ||
| modCtx.access_level_modifier()?.let { return@mapNotNull buildAccessLevelModifier(it) } | ||
|
|
||
| // Check for mutation modifier | ||
| modCtx.mutation_modifier()?.let { return@mapNotNull it.text } | ||
|
|
||
| // Other modifiers (static, final, lazy, etc.) | ||
| modCtx.text.takeIf { it.isNotEmpty() } | ||
| } ?: listOf() | ||
| } | ||
|
|
||
| // ==================== Struct Declaration ==================== | ||
|
|
||
| override fun enterStruct_declaration(ctx: Swift5Parser.Struct_declarationContext) { | ||
| val name = ctx.struct_name()?.text ?: return | ||
|
|
||
| // Extract attributes and access modifier directly from context | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) | ||
| val modifierAnnotations = if (accessModifier.isNotEmpty()) { | ||
| listOf(CodeAnnotation(Name = accessModifier)) | ||
| } else { | ||
| listOf() | ||
| } | ||
|
|
||
| val struct = CodeDataStruct( | ||
| NodeName = name, | ||
| Type = DataStructType.STRUCT, | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse inheritance | ||
| ctx.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| struct.Implements = buildInheritanceList(inheritanceCtx) | ||
| } | ||
|
|
||
| stack.addLast(struct) | ||
| } | ||
|
|
||
| override fun exitStruct_declaration(ctx: Swift5Parser.Struct_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Class Declaration ==================== | ||
|
|
||
| override fun enterClass_declaration(ctx: Swift5Parser.Class_declarationContext) { | ||
| val name = ctx.class_name()?.text ?: return | ||
|
|
||
| // Extract attributes and modifiers directly from context | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val modifiers = extractClassModifiers(ctx) | ||
|
|
||
| val modifierAnnotations = modifiers.filter { it.isNotEmpty() }.map { CodeAnnotation(Name = it) } | ||
|
|
||
| val classStruct = CodeDataStruct( | ||
| NodeName = name, | ||
| Type = DataStructType.CLASS, | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse inheritance | ||
| ctx.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| val inheritanceList = buildInheritanceList(inheritanceCtx) | ||
| if (inheritanceList.isNotEmpty()) { | ||
| // First item is typically the superclass for classes | ||
| classStruct.Extend = inheritanceList.first() | ||
| if (inheritanceList.size > 1) { | ||
| classStruct.Implements = inheritanceList.drop(1) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| stack.addLast(classStruct) | ||
| } | ||
|
|
||
| private fun extractClassModifiers(ctx: Swift5Parser.Class_declarationContext): List<String> { | ||
| val modifiers = mutableListOf<String>() | ||
|
|
||
| // Use parse-tree tokens instead of substring matching to avoid issues | ||
| // like "fileprivate" matching both "fileprivate" and "private" | ||
| ctx.access_level_modifier() | ||
| ?.let { buildAccessLevelModifier(it) } | ||
| ?.takeIf { it.isNotEmpty() } | ||
| ?.let { modifiers.add(it) } | ||
|
|
||
| if (ctx.FINAL() != null) { | ||
| modifiers.add("final") | ||
| } | ||
|
|
||
| return modifiers | ||
| } | ||
|
|
||
| override fun exitClass_declaration(ctx: Swift5Parser.Class_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Actor Declaration (Swift 5.5+) ==================== | ||
|
|
||
| override fun enterActor_declaration(ctx: Swift5Parser.Actor_declarationContext) { | ||
| val name = ctx.actor_name()?.text ?: return | ||
|
|
||
| // Extract attributes and modifiers | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) | ||
| val modifiers = mutableListOf<String>() | ||
|
|
||
| if (accessModifier.isNotEmpty()) { | ||
| modifiers.add(accessModifier) | ||
| } | ||
|
|
||
| // Check for distributed modifier | ||
| if (ctx.DISTRIBUTED() != null) { | ||
| modifiers.add("distributed") | ||
| } | ||
|
|
||
| val modifierAnnotations = modifiers.map { CodeAnnotation(Name = it) } | ||
|
|
||
| val actorStruct = CodeDataStruct( | ||
| NodeName = name, | ||
| Type = DataStructType.CLASS, // Actor is similar to class | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse inheritance | ||
| ctx.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| actorStruct.Implements = buildInheritanceList(inheritanceCtx) | ||
| } | ||
|
|
||
| stack.addLast(actorStruct) | ||
| } | ||
|
|
||
| override fun exitActor_declaration(ctx: Swift5Parser.Actor_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Enum Declaration ==================== | ||
|
|
||
| override fun enterEnum_declaration(ctx: Swift5Parser.Enum_declarationContext) { | ||
| val name = ctx.union_style_enum()?.enum_name()?.text | ||
| ?: ctx.raw_value_style_enum()?.enum_name()?.text | ||
| ?: return | ||
|
|
||
| // Extract attributes and access modifier directly from context | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) | ||
| val modifierAnnotations = if (accessModifier.isNotEmpty()) { | ||
| listOf(CodeAnnotation(Name = accessModifier)) | ||
| } else { | ||
| listOf() | ||
| } | ||
|
|
||
| val enumStruct = CodeDataStruct( | ||
| NodeName = name, | ||
| Type = DataStructType.ENUM, | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse inheritance/raw value type | ||
| ctx.union_style_enum()?.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| enumStruct.Implements = buildInheritanceList(inheritanceCtx) | ||
| } | ||
| ctx.raw_value_style_enum()?.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| val inheritanceList = buildInheritanceList(inheritanceCtx) | ||
| if (inheritanceList.isNotEmpty()) { | ||
| // First is typically the raw value type | ||
| enumStruct.Extend = inheritanceList.first() | ||
| if (inheritanceList.size > 1) { | ||
| enumStruct.Implements = inheritanceList.drop(1) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Parse enum cases | ||
| ctx.union_style_enum()?.union_style_enum_members()?.union_style_enum_member()?.forEach { member -> | ||
| member.union_style_enum_case_clause()?.union_style_enum_case_list()?.union_style_enum_case()?.forEach { caseCtx -> | ||
| val caseName = caseCtx.enum_case_name()?.text ?: return@forEach | ||
| enumStruct.Fields += CodeField( | ||
| TypeKey = caseName, | ||
| TypeType = caseCtx.tuple_type()?.text ?: "" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| ctx.raw_value_style_enum()?.raw_value_style_enum_members()?.raw_value_style_enum_member()?.forEach { member -> | ||
| member.raw_value_style_enum_case_clause()?.raw_value_style_enum_case_list()?.raw_value_style_enum_case()?.forEach { caseCtx -> | ||
| val caseName = caseCtx.enum_case_name()?.text ?: return@forEach | ||
| val rawValue = caseCtx.raw_value_assignment()?.raw_value_literal()?.text ?: "" | ||
| enumStruct.Fields += CodeField( | ||
| TypeKey = caseName, | ||
| TypeValue = rawValue | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| stack.addLast(enumStruct) | ||
| } | ||
|
|
||
| override fun exitEnum_declaration(ctx: Swift5Parser.Enum_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Protocol Declaration ==================== | ||
|
|
||
| override fun enterProtocol_declaration(ctx: Swift5Parser.Protocol_declarationContext) { | ||
| val name = ctx.protocol_name()?.text ?: return | ||
|
|
||
| // Extract attributes and access modifier directly from context | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) | ||
| val modifierAnnotations = if (accessModifier.isNotEmpty()) { | ||
| listOf(CodeAnnotation(Name = accessModifier)) | ||
| } else { | ||
| listOf() | ||
| } | ||
|
|
||
| val protocol = CodeDataStruct( | ||
| NodeName = name, | ||
| Type = DataStructType.INTERFACE, // Protocol is similar to Interface | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse inheritance (protocol can inherit from other protocols) | ||
| ctx.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| protocol.Implements = buildInheritanceList(inheritanceCtx) | ||
| } | ||
|
|
||
| stack.addLast(protocol) | ||
| } | ||
|
|
||
| override fun exitProtocol_declaration(ctx: Swift5Parser.Protocol_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Extension Declaration ==================== | ||
|
|
||
| override fun enterExtension_declaration(ctx: Swift5Parser.Extension_declarationContext) { | ||
| val typeName = ctx.type_identifier()?.text ?: return | ||
|
|
||
| // Extract attributes and access modifier directly from context | ||
| val annotations = buildAnnotationsFromAttributes(ctx.attributes()) | ||
| val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) | ||
| val modifierAnnotations = if (accessModifier.isNotEmpty()) { | ||
| listOf(CodeAnnotation(Name = accessModifier)) | ||
| } else { | ||
| listOf() | ||
| } | ||
|
|
||
| val extension = CodeDataStruct( | ||
| NodeName = typeName, | ||
| Type = DataStructType.CLASS, // Extension extends existing type | ||
| FilePath = filePath, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations + modifierAnnotations | ||
| ) | ||
|
|
||
| // Parse protocol conformance | ||
| ctx.type_inheritance_clause()?.let { inheritanceCtx -> | ||
| extension.Implements = buildInheritanceList(inheritanceCtx) | ||
| } | ||
|
|
||
| stack.addLast(extension) | ||
| } | ||
|
|
||
| override fun exitExtension_declaration(ctx: Swift5Parser.Extension_declarationContext) { | ||
| finishDataStruct() | ||
| } | ||
|
|
||
| // ==================== Function Declaration ==================== | ||
|
|
||
| override fun enterFunction_declaration(ctx: Swift5Parser.Function_declarationContext) { | ||
| val name = ctx.function_name()?.text ?: return | ||
|
|
||
| val returnTypeText = ctx.function_signature() | ||
| ?.function_result() | ||
| ?.type() | ||
| ?.text | ||
| ?.takeIf { it.isNotBlank() } | ||
|
|
||
| // Extract attributes and modifiers from function_head | ||
| val functionHead = ctx.function_head() | ||
| val annotations = buildAnnotationsFromAttributes(functionHead?.attributes()) | ||
| val modifiers = buildModifiersFromDeclarationModifiers(functionHead?.declaration_modifiers()).toMutableList() | ||
|
|
||
| val function = CodeFunction( | ||
| Name = name, | ||
| ReturnType = returnTypeText ?: "", | ||
| ReturnTypeRef = returnTypeText?.let { SwiftTypeRefBuilder.build(it) }, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations, | ||
| Modifiers = modifiers | ||
| ) | ||
|
|
||
| // Parse parameters | ||
| ctx.function_signature()?.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx -> | ||
| function.Parameters += buildParameter(paramCtx) | ||
| } | ||
|
|
||
| // Check for async (Swift 5.5+) | ||
| ctx.function_signature()?.async_clause()?.let { | ||
| function.Modifiers = function.Modifiers + "async" | ||
| } | ||
|
|
||
| // Check for throws/rethrows with typed throws support (Swift 6) | ||
| ctx.function_signature()?.throw_clause()?.let { throwClause -> | ||
| if (throwClause.RETHROWS() != null) { | ||
| function.Modifiers = function.Modifiers + "rethrows" | ||
| } else if (throwClause.THROWS() != null) { | ||
| // Check for typed throws: throws(ErrorType) | ||
| val errorType = throwClause.type()?.text | ||
| if (errorType != null) { | ||
| function.Modifiers = function.Modifiers + "throws($errorType)" | ||
| } else { | ||
| function.Modifiers = function.Modifiers + "throws" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| currentFunction = function | ||
|
|
||
| if (stack.isEmpty()) { | ||
| topLevelFunctions += function | ||
| } else { | ||
| val current = stack.last() | ||
| current.Functions = current.Functions + function | ||
| } | ||
| } | ||
|
|
||
| override fun exitFunction_declaration(ctx: Swift5Parser.Function_declarationContext) { | ||
| currentFunction = null | ||
| } |
There was a problem hiding this comment.
Preserve outer currentFunction when functions/initializers are nested.
currentFunction is overwritten on entry and nulled on exit, so nested functions (including ones inside initializers) will cause outer function calls after the nested declaration to be dropped. Use a stack so the previous function is restored on exit.
🐛 Proposed fix
- private var currentFunction: CodeFunction? = null
+ private val functionStack = ArrayDeque<CodeFunction>()
+ private var currentFunction: CodeFunction? = null
@@
- currentFunction = function
+ functionStack.addLast(function)
+ currentFunction = function
@@
- currentFunction = null
+ if (functionStack.isNotEmpty()) functionStack.removeLast()
+ currentFunction = functionStack.lastOrNull()
@@
- currentFunction = function
+ functionStack.addLast(function)
+ currentFunction = function
@@
- currentFunction = null
+ if (functionStack.isNotEmpty()) functionStack.removeLast()
+ currentFunction = functionStack.lastOrNull()Also applies to: 425-472
🤖 Prompt for AI Agents
In `@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt`
around lines 16 - 421, currentFunction is overwritten for nested
functions/initializers; preserve outer functions by replacing the single
currentFunction field with a stack: in enterFunction_declaration (and the
corresponding initializer handlers like enterInitializer_declaration) push the
existing currentFunction onto a new stack, set currentFunction to the new
CodeFunction, and on exitFunction_declaration (and exitInitializer_declaration)
pop the stack and restore the previous currentFunction (or set to null if stack
empty); update all places that assign/null currentFunction to use push/pop so
nested declarations restore outer contexts correctly.
| override fun enterProtocol_method_declaration(ctx: Swift5Parser.Protocol_method_declarationContext) { | ||
| val name = ctx.function_name()?.text ?: return | ||
|
|
||
| // Extract attributes and modifiers from function_head | ||
| val functionHead = ctx.function_head() | ||
| val annotations = buildAnnotationsFromAttributes(functionHead?.attributes()) | ||
| val modifiers = buildModifiersFromDeclarationModifiers(functionHead?.declaration_modifiers()) | ||
|
|
||
| val function = CodeFunction( | ||
| Name = name, | ||
| Position = ctx.toPosition(), | ||
| Annotations = annotations, | ||
| Modifiers = modifiers | ||
| ) | ||
|
|
||
| // Parse return type | ||
| ctx.function_signature()?.function_result()?.type()?.text?.let { returnType -> | ||
| function.ReturnType = returnType | ||
| function.ReturnTypeRef = SwiftTypeRefBuilder.build(returnType) | ||
| } | ||
|
|
||
| // Parse parameters | ||
| ctx.function_signature()?.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx -> | ||
| function.Parameters += buildParameter(paramCtx) | ||
| } | ||
|
|
||
| if (stack.isNotEmpty()) { | ||
| val current = stack.last() | ||
| current.Functions = current.Functions + function | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file exists and read the relevant lines
cd chapi-ast-swift && head -600 src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt | tail -40Repository: phodal/chapi
Length of output: 1644
🏁 Script executed:
# Let's also find the enterFunction_declaration to see if it handles async_clause/throw_clause
rg "enterFunction_declaration|async_clause|throw_clause" chapi-ast-swift/src/main/kotlin/ -A 5 -B 2Repository: phodal/chapi
Length of output: 2623
🏁 Script executed:
# Search for other references to modifiers handling in the same file
rg "function_signature|declaration_modifiers" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt -B 2 -A 3Repository: phodal/chapi
Length of output: 3320
Protocol method requirements drop async/throws modifiers.
enterProtocol_method_declaration reads parameters and return type but ignores async_clause and throw_clause, creating an inconsistency with how enterFunction_declaration handles them. Protocol methods can be declared with async and throws/rethrows requirements in Swift 5.5+, but these modifiers are not extracted.
♻️ Proposed fix
// Parse parameters
ctx.function_signature()?.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx ->
function.Parameters += buildParameter(paramCtx)
}
+
+ // Check for async (Swift 5.5+)
+ ctx.function_signature()?.async_clause()?.let {
+ function.Modifiers = function.Modifiers + "async"
+ }
+
+ // Check for throws/rethrows with typed throws support (Swift 6)
+ ctx.function_signature()?.throw_clause()?.let { throwClause ->
+ if (throwClause.RETHROWS() != null) {
+ function.Modifiers = function.Modifiers + "rethrows"
+ } else if (throwClause.THROWS() != null) {
+ val errorType = throwClause.type()?.text
+ function.Modifiers = function.Modifiers + (errorType?.let { "throws($it)" } ?: "throws")
+ }
+ }🤖 Prompt for AI Agents
In `@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt`
around lines 567 - 596, The protocol method visitor
enterProtocol_method_declaration is not capturing async/throws modifiers; update
it to mirror the logic used in enterFunction_declaration by inspecting
ctx.function_signature() for async_clause and throw_clause/throwing_specifier
(or the exact names used in your grammar), then set the corresponding fields on
the CodeFunction instance (e.g., function.Async = true and set function.Throws /
function.ReThrows or whatever properties your CodeFunction uses for throwing
modifiers). Ensure you pull these from ctx.function_signature()?.async_clause()
and ctx.function_signature()?.throw_clause() (or throwing_specifier) before
adding the function to the parent, so protocol requirements include async/throws
the same way as regular functions.
| override fun enterProtocol_property_declaration(ctx: Swift5Parser.Protocol_property_declarationContext) { | ||
| val varName = ctx.variable_name()?.text ?: return | ||
| val typeAnnotation = ctx.type_annotation()?.type()?.text ?: "" | ||
|
|
||
| // Extract attributes and modifiers from variable_declaration_head | ||
| val varHead = ctx.variable_declaration_head() | ||
| val annotations = buildAnnotationsFromAttributes(varHead?.attributes()) | ||
| val modifiers = buildModifiersFromDeclarationModifiers(varHead?.declaration_modifiers()) | ||
|
|
||
| val field = CodeField( | ||
| TypeKey = varName, | ||
| TypeType = typeAnnotation, | ||
| Annotations = annotations, | ||
| Modifiers = modifiers, | ||
| TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null | ||
| ) | ||
|
|
||
| // Check getter/setter requirements | ||
| ctx.getter_setter_keyword_block()?.let { block -> | ||
| if (block.getter_keyword_clause() != null) { | ||
| field.Modifiers = field.Modifiers + "get" | ||
| } | ||
| if (block.setter_keyword_clause() != null) { | ||
| field.Modifiers = field.Modifiers + "set" | ||
| } | ||
| } | ||
|
|
||
| addFieldToCurrentScope(field) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and check its overall structure
fd "SwiftFullIdentListener.kt" -x wc -l {}Repository: phodal/chapi
Length of output: 138
🏁 Script executed:
# Let's look at how regular variable declarations are handled in the same file
rg "enterVariable_declaration" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/ -A 20 -B 2Repository: phodal/chapi
Length of output: 2977
🏁 Script executed:
# Search for how modifiers are built in other parts of the code
rg "buildModifiersFromDeclarationModifiers" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/ -B 5 -A 10Repository: phodal/chapi
Length of output: 13814
🏁 Script executed:
# Check if "var" is added to modifiers anywhere else
rg '\+ "var"' chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/Repository: phodal/chapi
Length of output: 307
🏁 Script executed:
# Look at the class definition and CodeField to understand what Modifiers represents
rg "class CodeField" chapi-ast-swift/ -A 10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Get lines 599-627 to see the exact code under review
sed -n '599,627p' chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.ktRepository: phodal/chapi
Length of output: 1342
🏁 Script executed:
# Search for all property declaration handlers
rg "enterProtocol.*property|enterComputed.*property|enter.*Property" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/ -B 2 -A 15Repository: phodal/chapi
Length of output: 2303
🏁 Script executed:
# Check constant_declaration to see if it adds "let"
rg "enterConstant_declaration" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/ -A 20 | head -40Repository: phodal/chapi
Length of output: 2812
🏁 Script executed:
# Search for all places where modifiers are assigned to CodeField
rg "CodeField\(" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt -A 5 | grep -A 2 "Modifiers"Repository: phodal/chapi
Length of output: 465
🏁 Script executed:
# Search for other property-related handlers
rg "enterClass_property|enterComputed_property|enterLazy_variable" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Check if there's a class property declaration handler
rg "Class_property_declaration|Property_.*declaration" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/ -B 2 -A 10Repository: phodal/chapi
Length of output: 38
🏁 Script executed:
# Look at how the file structure is organized - check all override enterXxx functions related to properties/variables
rg "override fun enter" chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt | grep -i "property\|variable\|constant"Repository: phodal/chapi
Length of output: 349
Add var modifier to protocol properties for consistency.
Protocol properties extract from variable_declaration_head but omit the var keyword from modifiers, unlike variable_declaration (which adds "var") and constant_declaration (which adds "let"). In Swift, protocol properties are always declared with var, so this should be included in the modifiers list for consistency.
Suggested fix
- val modifiers = buildModifiersFromDeclarationModifiers(varHead?.declaration_modifiers())
+ val modifiers = buildModifiersFromDeclarationModifiers(varHead?.declaration_modifiers()) + "var"🤖 Prompt for AI Agents
In `@chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt`
around lines 599 - 627, In enterProtocol_property_declaration in
SwiftFullIdentListener, the modifiers for protocol properties omit the required
"var" token; update the logic that builds the modifiers (using
variable_declaration_head / buildModifiersFromDeclarationModifiers) to ensure
"var" is added to field.Modifiers (similar to how variable_declaration adds
"var" and constant_declaration adds "let"), e.g., append or include "var" to the
Modifiers list on the created CodeField before handling
getter_setter_keyword_block and calling addFieldToCurrentScope.
Summary
This PR adds comprehensive Swift language parsing support to Chapi, covering both Swift 5 and Swift 6 syntax features.
Swift 5 Features
Swift 6 Features
throws(ErrorType)syntax for precise error handlingdistributed actordeclarationsconsumingandborrowingparameter modifiers~Copyablesyntax support in type inheritance#externalMacroand macro syntaxNew Keywords Added
async,await,actor,distributed,nonisolated,isolated,sendingconsuming,borrowing,copy,consume,discardmacro,freestanding,attached,#externalMacropackageTest plan
All 25 tests passing:
Swift 6 specific tests:
shouldParseTypedThrows()shouldParseAsyncFunctions()shouldParseActorDeclaration()shouldParseOwnershipModifiers()shouldParseNonisolatedModifier()README updated with Swift language coverage.
Closes #49.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.