Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Sources/SwiftLexicalLookup/IdentifiableSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ extension AccessorParametersSyntax: IdentifiableSyntax {
}
}

extension EnumCaseParameterSyntax: IdentifiableSyntax {
var identifier: TokenSyntax {
secondName ?? firstName ?? TokenSyntax(.wildcard, presence: .missing)
}
}

extension GenericParameterSyntax: IdentifiableSyntax {
var identifier: TokenSyntax {
name
Expand Down
71 changes: 71 additions & 0 deletions Sources/SwiftLexicalLookup/Scopes/EnumCaseDeclSyntax.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

@_spi(Experimental) extension EnumCaseDeclSyntax: ScopeSyntax {
/// Associated-value parameter names introduced by this case declaration.
///
/// Names are position-gated and only visible inside default-value expressions
/// of subsequent parameters within the same case element, e.g.:
/// ```swift
/// enum E {
/// case foo(x: Int, y: Int = x) // `x` visible in `y`'s default
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this true? As far as I tried, x is not visible from the default value of y. Am I missing anything?

Image

/// }
/// ```
/// Parameters from one case element are not visible inside another.
@_spi(Experimental) public var defaultIntroducedNames: [LookupName] {
elements.flatMap { element in
element.parameterClause?.parameters.flatMap { param in
LookupName.getNames(from: param)
} ?? []
}
}

@_spi(Experimental) public var scopeDebugName: String {
"EnumCaseDeclScope"
}

/// Looks up `identifier` from inside this case declaration.
///
/// Only introduces names when lookup originates from a parameter's
/// default-value expression. Names visible are those of all prior
/// parameters within the same case element.
///
/// Lookup from outside the declaration (e.g. in the enum body) is
/// forwarded to the parent scope with no names introduced.
@_spi(Experimental) public func lookup(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
) -> [LookupResult] {
for element in elements {
guard let paramClause = element.parameterClause else { continue }

for (index, param) in paramClause.parameters.enumerated() {
guard let defaultValue = param.defaultValue,
defaultValue.range.contains(lookUpPosition)
else { continue }

// Collect names from params before this one in the same element.
let priorNames = paramClause.parameters.prefix(index).flatMap { prior in
LookupName.getNames(from: prior)
}.filter { $0.refersTo(identifier) }

return LookupResult.getResultArray(for: self, withNames: priorNames)
+ lookupInParent(identifier, at: lookUpPosition, with: config)
}
}

return lookupInParent(identifier, at: lookUpPosition, with: config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ Some scopes share common behavior and can be further generalized as follows:
- **Function Declaration Scope** (`FunctionDeclSyntax`): With generic parameters scope. Introduces names from its parameters.
- **Subscript Declaration Syntax** (`SubscriptDeclSyntax`): With generic parameters scope. Introduces names from its parameters.
- **Type Alias Declaration Scope** (`TypeAliasDeclSyntax`): With generic parameters scope. Does not introduce any names.
- **Enum Case Declaration Scope** (`EnumCaseDeclSyntax`): Introduces names from each case element's associated-value parameters. A parameter name is only visible inside default-value expressions of **subsequent** parameters in the same case declaration (position-based availability). No names are introduced to any outer scope.
64 changes: 64 additions & 0 deletions Tests/SwiftLexicalLookupTest/NameLookupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1124,4 +1124,68 @@ final class TestNameLookup: XCTestCase {
useNilAsTheParameter: true
)
}
// MARK: - EnumCaseDeclSyntax scope tests

func testEnumCaseAssociatedValueParamLookupBasic() {
assertLexicalNameLookup(
source: """
enum E {
case foo(1️⃣x: Int, y: Int = 2️⃣x)
}
""",
references: [
"2️⃣": [
.fromScope(EnumCaseDeclSyntax.self, expectedNames: ["1️⃣"]),
.lookForMembers(EnumDeclSyntax.self),
]
],
expectedResultTypes: .all(EnumCaseParameterSyntax.self)
)
}

func testEnumCaseFirstParamHasNoPriorNames() {
assertLexicalNameLookup(
source: """
enum E {
case foo(x: Int = 1️⃣y, y: Int)
}
""",
references: [
// `y` referenced in the default of `x` — `y` is declared after `x`, so invisible
"1️⃣": [.lookForMembers(EnumDeclSyntax.self)]
]
)
}

func testEnumCaseParamNotVisibleOutsideCaseDecl() {
assertLexicalNameLookup(
source: """
enum E {
case foo(x: Int)

func bar() {
let _ = 1️⃣x
}
}
""",
references: [
// case payload `x` is not on the lookup chain inside a member function
"1️⃣": [.lookForMembers(EnumDeclSyntax.self)]
]
)
}

func testEnumCaseMultiElementParamsAreIsolated() {
assertLexicalNameLookup(
source: """
enum E {
case foo(x: Int), bar(y: Int = 1️⃣x)
}
""",
references: [
// params from `foo` element aren't visible in `bar`'s default
"1️⃣": [.lookForMembers(EnumDeclSyntax.self)]
]
)
}
}