From fbdefc5e3610f9bee0916c6c73149d435fd2592d Mon Sep 17 00:00:00 2001 From: Ronit Sabhaya Date: Mon, 23 Feb 2026 11:06:35 -0600 Subject: [PATCH] [SwiftLexicalLookup] Add ScopeSyntax conformance for EnumCaseDeclSyntax --- .../IdentifiableSyntax.swift | 6 ++ .../Scopes/EnumCaseDeclSyntax.swift | 71 +++++++++++++++++++ .../SwiftLexicalLookup.docc/LookupRules.md | 1 + .../NameLookupTests.swift | 64 +++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 Sources/SwiftLexicalLookup/Scopes/EnumCaseDeclSyntax.swift diff --git a/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift b/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift index fa053a00699..bf710047c9e 100644 --- a/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift +++ b/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift @@ -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 diff --git a/Sources/SwiftLexicalLookup/Scopes/EnumCaseDeclSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/EnumCaseDeclSyntax.swift new file mode 100644 index 00000000000..148eea1b0e3 --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/EnumCaseDeclSyntax.swift @@ -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 + /// } + /// ``` + /// 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) + } +} diff --git a/Sources/SwiftLexicalLookup/SwiftLexicalLookup.docc/LookupRules.md b/Sources/SwiftLexicalLookup/SwiftLexicalLookup.docc/LookupRules.md index 554da7bb79f..f7d2ab220e5 100644 --- a/Sources/SwiftLexicalLookup/SwiftLexicalLookup.docc/LookupRules.md +++ b/Sources/SwiftLexicalLookup/SwiftLexicalLookup.docc/LookupRules.md @@ -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. diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 477105951b7..4464e61d738 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -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)] + ] + ) + } }