Skip to content

Commit d0cb13e

Browse files
authored
[extract] typed throws and attributes (#894)
1 parent 673c817 commit d0cb13e

7 files changed

Lines changed: 321 additions & 23 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftSyntax
16+
17+
extension ExtractedNominalType {
18+
public var attributeList: AttributeListSyntax {
19+
swiftNominal.syntax.attributes
20+
}
21+
22+
public func attribute(named name: String) -> AttributeSyntax? {
23+
Self.first(attribute: name, in: attributeList)
24+
}
25+
26+
static func first(attribute name: String, in attributes: AttributeListSyntax) -> AttributeSyntax? {
27+
for element in attributes {
28+
guard let attr = element.as(AttributeSyntax.self),
29+
let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text,
30+
attrName == name
31+
else {
32+
continue
33+
}
34+
return attr
35+
}
36+
return nil
37+
}
38+
}
39+
40+
extension ExtractedFunc {
41+
public var attributeList: AttributeListSyntax? {
42+
if let n = swiftDecl.as(FunctionDeclSyntax.self) { return n.attributes }
43+
if let n = swiftDecl.as(InitializerDeclSyntax.self) { return n.attributes }
44+
if let n = swiftDecl.as(VariableDeclSyntax.self) { return n.attributes }
45+
if let n = swiftDecl.as(SubscriptDeclSyntax.self) { return n.attributes }
46+
if let n = swiftDecl.as(EnumCaseDeclSyntax.self) { return n.attributes }
47+
return nil
48+
}
49+
50+
public func attribute(named name: String) -> AttributeSyntax? {
51+
guard let list = attributeList else { return nil }
52+
return ExtractedNominalType.first(attribute: name, in: list)
53+
}
54+
}
55+
56+
extension ExtractedEnumCase {
57+
public var attributeList: AttributeListSyntax? {
58+
swiftDecl.as(EnumCaseDeclSyntax.self)?.attributes
59+
}
60+
61+
public func attribute(named name: String) -> AttributeSyntax? {
62+
guard let list = attributeList else { return nil }
63+
return ExtractedNominalType.first(attribute: name, in: list)
64+
}
65+
}

‎Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift‎

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public struct SwiftFunctionSignature: Equatable {
2727
public var parameters: [SwiftParameter]
2828
public var result: SwiftResult
2929
public var effectSpecifiers: [SwiftEffectSpecifier]
30+
public var thrownTypedError: SwiftType?
3031
public var genericParameters: [SwiftGenericParameterDeclaration]
3132
public var genericRequirements: [SwiftGenericRequirement]
3233

@@ -38,6 +39,10 @@ public struct SwiftFunctionSignature: Equatable {
3839
effectSpecifiers.contains(.throws)
3940
}
4041

42+
public var isTypedThrowing: Bool {
43+
thrownTypedError != nil
44+
}
45+
4146
/// Whether any parameter is variadic (`T...`).
4247
public var hasVariadicParams: Bool {
4348
parameters.contains(where: \.isVariadic)
@@ -53,13 +58,15 @@ public struct SwiftFunctionSignature: Equatable {
5358
parameters: [SwiftParameter],
5459
result: SwiftResult,
5560
effectSpecifiers: [SwiftEffectSpecifier],
61+
thrownTypedError: SwiftType? = nil,
5662
genericParameters: [SwiftGenericParameterDeclaration],
5763
genericRequirements: [SwiftGenericRequirement]
5864
) {
5965
self.selfParameter = selfParameter
6066
self.parameters = parameters
6167
self.result = result
6268
self.effectSpecifiers = effectSpecifiers
69+
self.thrownTypedError = thrownTypedError
6370
self.genericParameters = genericParameters
6471
self.genericRequirements = genericRequirements
6572
}
@@ -120,7 +127,7 @@ extension SwiftFunctionSignature {
120127
whereClause: node.genericWhereClause,
121128
lookupContext: lookupContext
122129
)
123-
let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
130+
let (parameters, effectSpecifiers, thrownTypedError) = try Self.translateFunctionSignature(
124131
node.signature,
125132
lookupContext: lookupContext
126133
)
@@ -138,6 +145,7 @@ extension SwiftFunctionSignature {
138145
parameters: parameters,
139146
result: SwiftResult(convention: .direct, type: type),
140147
effectSpecifiers: effectSpecifiers,
148+
thrownTypedError: thrownTypedError,
141149
genericParameters: genericParams,
142150
genericRequirements: genericRequirements
143151
)
@@ -205,8 +213,8 @@ extension SwiftFunctionSignature {
205213
selfParameter = nil
206214
}
207215

208-
// Translate the parameters.
209-
let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
216+
// Translate the function signature
217+
let (parameters, effectSpecifiers, thrownTypedError) = try Self.translateFunctionSignature(
210218
node.signature,
211219
lookupContext: lookupContext
212220
)
@@ -227,6 +235,7 @@ extension SwiftFunctionSignature {
227235
parameters: parameters,
228236
result: result,
229237
effectSpecifiers: effectSpecifiers,
238+
thrownTypedError: thrownTypedError,
230239
genericParameters: genericParams,
231240
genericRequirements: genericRequirements
232241
)
@@ -289,11 +298,16 @@ extension SwiftFunctionSignature {
289298
}
290299

291300
/// Translate the function signature, returning the list of translated
292-
/// parameters and effect specifiers.
301+
/// parameters, its effect specifiers, and the error type of a typed
302+
/// `throws(E)` clause if present.
293303
public static func translateFunctionSignature(
294304
_ signature: FunctionSignatureSyntax,
295305
lookupContext: SwiftTypeLookupContext
296-
) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) {
306+
) throws -> (
307+
parameters: [SwiftParameter],
308+
effectSpecifiers: [SwiftEffectSpecifier],
309+
thrownTypedError: SwiftType?
310+
) {
297311
var effectSpecifiers = [SwiftEffectSpecifier]()
298312
if signature.effectSpecifiers?.throwsClause != nil {
299313
effectSpecifiers.append(.throws)
@@ -302,11 +316,16 @@ extension SwiftFunctionSignature {
302316
effectSpecifiers.append(.async)
303317
}
304318

319+
let thrownTypedError = SwiftType.thrownTypedError(
320+
from: signature.effectSpecifiers?.throwsClause,
321+
lookupContext: lookupContext
322+
)
323+
305324
let parameters = try signature.parameterClause.parameters.map { param in
306325
try SwiftParameter(param, lookupContext: lookupContext)
307326
}
308327

309-
return (parameters, effectSpecifiers)
328+
return (parameters, effectSpecifiers, thrownTypedError)
310329
}
311330

312331
public init(
@@ -331,21 +350,22 @@ extension SwiftFunctionSignature {
331350
}
332351
let valueType = try SwiftType(varTypeNode, lookupContext: lookupContext)
333352

334-
var effectSpecifiers: [SwiftEffectSpecifier]? = nil
353+
var accessorEffects: AccessorEffects? = nil
335354
switch binding.accessorBlock?.accessors {
336355
case .getter(let getter):
337356
if let getter = getter.as(AccessorDeclSyntax.self) {
338-
effectSpecifiers = try Self.effectSpecifiers(from: getter)
357+
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
339358
}
340359
case .accessors(let accessors):
341360
if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
342-
effectSpecifiers = try Self.effectSpecifiers(from: getter)
361+
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
343362
}
344363
default:
345364
break
346365
}
347366

348-
self.effectSpecifiers = effectSpecifiers ?? []
367+
self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? []
368+
self.thrownTypedError = accessorEffects?.thrownTypedError
349369

350370
if isSet {
351371
self.parameters = [
@@ -388,21 +408,22 @@ extension SwiftFunctionSignature {
388408
return p
389409
}
390410

391-
var effectSpecifiers: [SwiftEffectSpecifier]? = nil
411+
var accessorEffects: AccessorEffects? = nil
392412
switch subscriptNode.accessorBlock?.accessors {
393413
case .getter(let getter):
394414
if let getter = getter.as(AccessorDeclSyntax.self) {
395-
effectSpecifiers = try Self.effectSpecifiers(from: getter)
415+
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
396416
}
397417
case .accessors(let accessors):
398418
if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
399-
effectSpecifiers = try Self.effectSpecifiers(from: getter)
419+
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
400420
}
401421
default:
402422
break
403423
}
404424

405-
self.effectSpecifiers = effectSpecifiers ?? []
425+
self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? []
426+
self.thrownTypedError = accessorEffects?.thrownTypedError
406427

407428
if isSet {
408429
nodeParameters.append(SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType))
@@ -416,15 +437,29 @@ extension SwiftFunctionSignature {
416437
self.genericRequirements = []
417438
}
418439

419-
private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] {
440+
struct AccessorEffects {
441+
var effectSpecifiers: [SwiftEffectSpecifier] = []
442+
var thrownTypedError: SwiftType? = nil
443+
}
444+
445+
private static func translateEffectSpecifiers(
446+
from decl: AccessorDeclSyntax,
447+
lookupContext: SwiftTypeLookupContext
448+
) -> AccessorEffects {
420449
var effectSpecifiers = [SwiftEffectSpecifier]()
421450
if decl.effectSpecifiers?.throwsClause != nil {
422451
effectSpecifiers.append(.throws)
423452
}
424453
if decl.effectSpecifiers?.asyncSpecifier != nil {
425454
effectSpecifiers.append(.async)
426455
}
427-
return effectSpecifiers
456+
457+
let thrownTypedError = SwiftType.thrownTypedError(
458+
from: decl.effectSpecifiers?.throwsClause,
459+
lookupContext: lookupContext
460+
)
461+
462+
return AccessorEffects(effectSpecifiers: effectSpecifiers, thrownTypedError: thrownTypedError)
428463
}
429464

430465
private static func variableSelfParameter(

‎Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ public struct SwiftFunctionType: Equatable {
2727

2828
public var effectSpecifiers: [SwiftEffectSpecifier] = []
2929

30-
public var thrownTypedError: SwiftType? = nil
31-
3230
public var isAsync: Bool { effectSpecifiers.contains(.async) }
31+
3332
public var isThrowing: Bool { effectSpecifiers.contains(.throws) }
3433
public var isTypedThrowing: Bool { thrownTypedError != nil }
34+
public var thrownTypedError: SwiftType? = nil
3535

3636
public init(
3737
convention: Convention,
@@ -95,9 +95,10 @@ extension SwiftFunctionType {
9595
}
9696
if let throwsClause = node.effectSpecifiers?.throwsClause {
9797
effectSpecifiers.append(.throws)
98-
if let errorTypeNode = throwsClause.type {
99-
self.thrownTypedError = try? SwiftType(errorTypeNode, lookupContext: lookupContext)
100-
}
98+
self.thrownTypedError = SwiftType.thrownTypedError(
99+
from: throwsClause,
100+
lookupContext: lookupContext
101+
)
101102
}
102103
self.effectSpecifiers = effectSpecifiers
103104
}

‎Sources/SwiftExtract/SwiftTypes/SwiftType.swift‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,18 @@ extension SwiftNominalType {
339339
}
340340
}
341341

342+
extension SwiftType {
343+
public static func thrownTypedError(
344+
from throwsClause: ThrowsClauseSyntax?,
345+
lookupContext: SwiftTypeLookupContext
346+
) -> SwiftType? {
347+
guard let errorTypeNode = throwsClause?.type else {
348+
return nil
349+
}
350+
return try? SwiftType(errorTypeNode, lookupContext: lookupContext)
351+
}
352+
}
353+
342354
extension SwiftType {
343355
public init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws {
344356
var knownTypes: SwiftKnownTypes {

0 commit comments

Comments
 (0)