Skip to content

feat(ast): add Swift 5/6 parser module with comprehensive syntax support#50

Merged
phodal merged 5 commits intomasterfrom
feat/swift5-parser
Jan 20, 2026
Merged

feat(ast): add Swift 5/6 parser module with comprehensive syntax support#50
phodal merged 5 commits intomasterfrom
feat/swift5-parser

Conversation

@phodal
Copy link
Copy Markdown
Owner

@phodal phodal commented Jan 20, 2026

Summary

This PR adds comprehensive Swift language parsing support to Chapi, covering both Swift 5 and Swift 6 syntax features.

Swift 5 Features

  • Import declarations with kind support (class, struct, enum, etc.)
  • Struct, Class, Enum, Protocol declarations
  • Function declarations with parameters, return types
  • Initializer/Deinitializer parsing
  • Variable/Constant declarations
  • Extension declarations
  • Attributes and access modifiers
  • Type inheritance and protocol conformance
  • Nested types support
  • Function call expressions
  • Comprehensive type reference parsing (generics, optionals, arrays, dictionaries, tuples, function types)

Swift 6 Features

  • Typed throws: throws(ErrorType) syntax for precise error handling
  • Async/Await: Full async function support
  • Actors: Both regular and distributed actor declarations
  • Ownership modifiers: consuming and borrowing parameter modifiers
  • Noncopyable types: ~Copyable syntax support in type inheritance
  • Macro declarations: Support for #externalMacro and macro syntax

New Keywords Added

  • Concurrency: async, await, actor, distributed, nonisolated, isolated, sending
  • Ownership: consuming, borrowing, copy, consume, discard
  • Macros: macro, freestanding, attached, #externalMacro
  • Other: package

Test plan

All 25 tests passing:

  • 12 SwiftAnalyserTest tests
  • 5 Swift6FeaturesTest tests
  • 8 SwiftTypeRefBuilderTest tests

Swift 6 specific tests:

  • shouldParseTypedThrows()
  • shouldParseAsyncFunctions()
  • shouldParseActorDeclaration()
  • shouldParseOwnershipModifiers()
  • shouldParseNonisolatedModifier()

README updated with Swift language coverage.

Closes #49.

Summary by CodeRabbit

  • New Features
    • Added Swift language support: parsing, analysis, AST generation, and type parsing; included Swift module in the build and added Thrift to IDL tiers.
  • Tests
    • Added comprehensive Swift test suite covering parsing, types, and Swift 6 features (async/await, typed throws, actors, ownership).
  • Documentation
    • Updated README language table and usage examples to list Swift (5,6) and support details.
  • Chores
    • Bumped example dependency versions and added coverage aggregation for the Swift module.

✏️ Tip: You can customize this high-level summary in your review settings.

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.
Copilot AI review requested due to automatic review settings January 20, 2026 12:08
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 20, 2026

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

Adds a new Gradle module :chapi-ast-swift with ANTLR-based Swift lexer/parser and support classes, a Kotlin analyser and listener producing CodeContainer, a Swift type-ref builder, tests, Gradle build/config updates, README entries, and jacoco/settings integration.

Changes

Cohort / File(s) Summary
Project config & docs
README.md, settings.gradle.kts, build.gradle.kts
README updated with Swift and Thrift entries and dependency bumps; added :chapi-ast-swift to settings; added jacoco aggregation entry in root build.
Module build
chapi-ast-swift/build.gradle.kts
New Gradle configuration: applies antlr plugin, ANTLR and runtime dependencies, JUnit5 test setup, generated source dir, generateGrammarSource task, and Kotlin compile dependency on ANTLR generation.
ANTLR grammars
chapi-ast-swift/src/main/antlr/Swift5Lexer.g4, .../Swift5Parser.g4
Added full lexer and parser grammars (Swift6-capable) covering tokens, literals, string interpolation modes, directives, expressions, statements, declarations, types, generics, macros, ownership modifiers, and many Swift constructs.
ANTLR support classes
chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java, .../SwiftSupportLexer.java
New SwiftSupport parser helper with operator/operator-character BitSets and operator/statement-separation utilities; SwiftSupportLexer adds a parenthesis stack and reset behavior.
Analyser & listener
chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftAnalyser.kt, .../SwiftFullIdentListener.kt
New SwiftAnalyser (implements Analyser) that parses input via ANTLR and walks parse tree; SwiftFullIdentListener collects imports, types, functions, fields, positions and builds a CodeContainer.
Type ref builder
chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt
New SwiftTypeRefBuilder to parse Swift type expressions into CodeTypeRef (optionals, arrays, dictionaries, tuples, functions, generics, protocol compositions, metatypes, inout, etc.).
Tests
chapi-ast-swift/src/test/kotlin/chapi/ast/swiftast/SwiftAnalyserTest.kt
Comprehensive tests added for imports, structs/classes/enums, functions, initializers, Swift 6 features (typed throws, async, actors, ownership modifiers), and type-ref parsing.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I nibble tokens in moonlit code,
Grammars sprout on a parse-twined road,
Actors, async, and typed throws gleam,
I hop through ASTs and chase a dream,
Swift blossoms mapped in my small codeteam.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a Swift 5/6 parser module with comprehensive syntax support, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from issue #49: new Gradle module :chapi-ast-swift created with ANTLR-based parser, SwiftAnalyser and listener implemented to extract imports/types/functions, comprehensive tests added (25 tests passing), and README updated with Swift coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing Swift parser support as specified in issue #49. File additions include ANTLR grammar files, Kotlin/Java parser support classes, comprehensive tests, and necessary build configuration updates. No unrelated or extraneous changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread README.md Outdated
Comment on lines +27 to +30
| HTTP API decl | ✅ | 🆕 | | ✅ | ✅ | 🆕 | ✅ | ✅ | 🆕 | ✅ | |
| Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ | | ✅ | 🆕 |
| Function calls | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | | | ✅ | |
| Arch/package | ✅ | | | ✅ | ✅ | 🆕 | | ✅ | ✅ | ✅ | |
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
| HTTP API decl || 🆕 | ||| 🆕 | | | 🆕 || |
| Syntax parsing ||||||| | ||| 🆕 |
| Function calls ||| |||| | | || |
| Arch/package || |||| 🆕 | | ||| |
| HTTP API decl || 🆕 | 🆕 ||| 🆕 | | | 🆕 || |
| Syntax parsing ||||||| | ||| 🆕 |
| Function calls ||| 🆕 |||| | | || |
| Arch/package || |||| 🆕 | | ||| |

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +140 to +141
implementation "com.phodal.chapi:chapi-ast-java:2.5.2"
implementation "com.phodal.chapi:chapi-domain:2.5.2"
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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"

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 20, 2026

Codecov Report

❌ Patch coverage is 68.81119% with 223 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.74%. Comparing base (960cf73) to head (d912c53).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
...otlin/chapi/ast/swiftast/SwiftFullIdentListener.kt 66.97% 33 Missing and 112 partials ⚠️
...n/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt 56.43% 22 Missing and 22 partials ⚠️
...ft/src/main/java/chapi/ast/antlr/SwiftSupport.java 80.62% 18 Missing and 13 partials ⚠️
...c/main/java/chapi/ast/antlr/SwiftSupportLexer.java 50.00% 3 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.
@phodal
Copy link
Copy Markdown
Owner Author

phodal commented Jan 20, 2026

augment review

@phodal phodal changed the title feat(ast): add Swift5 parser module feat(ast): add Swift 5/6 parser module with comprehensive syntax support Jan 20, 2026
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Jan 20, 2026

🤖 Augment PR Summary

Summary: Adds first-class Swift parsing support (Swift 5 + Swift 6 syntax) to Chapi via a new Gradle module.

Changes:

  • Introduced :chapi-ast-swift module with ANTLR-based Swift lexer/parser grammars.
  • Added Swift parsing runtime helpers (SwiftSupport/SwiftSupportLexer) used by grammar predicates.
  • Implemented Kotlin analyser + listener (SwiftAnalyser, SwiftFullIdentListener) to build CodeContainer models (imports, structs/classes/enums/protocols/extensions, functions, fields, calls).
  • Added SwiftTypeRefBuilder to map Swift type strings (generics/optionals/arrays/dicts/tuples/functions/etc.) into CodeTypeRef.
  • Registered the new module in settings.gradle.kts and aggregated coverage in the root build.gradle.kts.
  • Expanded README language coverage table and documented supported Swift versions/features.
  • Added comprehensive unit tests covering Swift 5 parsing, Swift 6 features (typed throws/async/actors/ownership), and type-ref parsing.

Technical Notes: The Swift grammar includes Swift 6 constructs (typed throws, distributed actor, ownership modifiers, macros) and relies on custom operator/whitespace classification helpers during parsing.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


int start = tokens.index();
Token currentToken = tokens.get(start);
Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

val trimmed = typeText.trim()

// Handle optional types (suffix ? or !)
if (trimmed.endsWith("?") && !trimmed.startsWith("(")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional handling skips types that start with (, so parenthesized optionals like (Int, String)? or (() -> Void)? won’t be marked nullable by SwiftTypeRefBuilder. This can lead to incorrect ReturnTypeRef/parameter type modeling for common Swift signatures.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

val postfixExpr = ctx.parent as? Swift5Parser.Postfix_expressionContext ?: return
val primaryExpr = postfixExpr.primary_expression()

val functionName = primaryExpr?.unqualified_name()?.identifier()?.text ?: primaryExpr?.text ?: return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread chapi-ast-swift/src/main/antlr/Swift5Parser.g4
Comment thread chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt Outdated
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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +985 to +987
access_level_modifier
: (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (LPAREN SET RPAREN)?
;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.g4" | head -20

Repository: 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.g4

Repository: phodal/chapi

Length of output: 109


🏁 Script executed:

sed -n '980,995p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: phodal/chapi

Length of output: 292


🏁 Script executed:

sed -n '1535,1550p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: phodal/chapi

Length of output: 297


🏁 Script executed:

sed -n '1540,1650p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: phodal/chapi

Length of output: 2086


🏁 Script executed:

wc -l ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4

Repository: phodal/chapi

Length of output: 107


🏁 Script executed:

grep -n "PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4

Repository: phodal/chapi

Length of output: 91


🏁 Script executed:

grep -n "PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: phodal/chapi

Length of output: 38


🏁 Script executed:

cat ./chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 | head -110 | tail -20

Repository: phodal/chapi

Length of output: 612


🏁 Script executed:

grep -n "| PACKAGE" ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: 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.g4

Repository: phodal/chapi

Length of output: 280


🏁 Script executed:

sed -n '1612,1620p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: phodal/chapi

Length of output: 203


🏁 Script executed:

sed -n '1600,1625p' ./chapi-ast-swift/src/main/antlr/Swift5Parser.g4

Repository: 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.

Suggested change
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.

Comment on lines +134 to +152
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the SwiftSupport.java file
find . -name "SwiftSupport.java" -type f

Repository: 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 -40

Repository: 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 -5

Repository: 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.java

Repository: 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.java

Repository: 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.java

Repository: 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.java

Repository: 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 -20

Repository: 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.java

Repository: phodal/chapi

Length of output: 2081


🏁 Script executed:

# Search for where isLeftOperatorWS is called
rg "isLeftOperatorWS" --type java

Repository: phodal/chapi

Length of output: 533


🏁 Script executed:

# Search for where isRightOperatorWS is called
rg "isRightOperatorWS" --type java

Repository: phodal/chapi

Length of output: 537


🏁 Script executed:

# Look for Swift5Parser to understand token channels
find . -name "Swift5Parser.java" -o -name "*.g4" | grep -i swift

Repository: phodal/chapi

Length of output: 152


🏁 Script executed:

# Check if there's a Swift grammar file
find . -name "*.g4" -type f

Repository: 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 -30

Repository: 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 -40

Repository: 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 -50

Repository: 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.java

Repository: 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 2

Repository: 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 -30

Repository: 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.java

Repository: 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.java

Repository: 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 2

Repository: 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.

Suggested change
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.

Comment on lines +16 to +421
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +567 to +596
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 -40

Repository: 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 2

Repository: 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 3

Repository: 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.

Comment on lines +599 to +627
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)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 2

Repository: 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 10

Repository: 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 10

Repository: 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.kt

Repository: 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 15

Repository: 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 -40

Repository: 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 10

Repository: 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.

@phodal phodal merged commit b1ba8e7 into master Jan 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ast): add Swift5 parser module

2 participants