Skip to content
Merged
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
65 changes: 65 additions & 0 deletions Sources/SwiftExtract/ExtractedDecls+Attributes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

extension ExtractedNominalType {
public var attributeList: AttributeListSyntax {
swiftNominal.syntax.attributes
}

public func attribute(named name: String) -> AttributeSyntax? {
Self.first(attribute: name, in: attributeList)
}

static func first(attribute name: String, in attributes: AttributeListSyntax) -> AttributeSyntax? {
for element in attributes {
guard let attr = element.as(AttributeSyntax.self),
let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text,
attrName == name
else {
continue
}
return attr
}
return nil
}
}

extension ExtractedFunc {
public var attributeList: AttributeListSyntax? {
if let n = swiftDecl.as(FunctionDeclSyntax.self) { return n.attributes }
if let n = swiftDecl.as(InitializerDeclSyntax.self) { return n.attributes }
if let n = swiftDecl.as(VariableDeclSyntax.self) { return n.attributes }
if let n = swiftDecl.as(SubscriptDeclSyntax.self) { return n.attributes }
if let n = swiftDecl.as(EnumCaseDeclSyntax.self) { return n.attributes }
return nil
}

public func attribute(named name: String) -> AttributeSyntax? {
guard let list = attributeList else { return nil }
return ExtractedNominalType.first(attribute: name, in: list)
}
}

extension ExtractedEnumCase {
public var attributeList: AttributeListSyntax? {
swiftDecl.as(EnumCaseDeclSyntax.self)?.attributes
}

public func attribute(named name: String) -> AttributeSyntax? {
guard let list = attributeList else { return nil }
return ExtractedNominalType.first(attribute: name, in: list)
}
}
67 changes: 51 additions & 16 deletions Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public struct SwiftFunctionSignature: Equatable {
public var parameters: [SwiftParameter]
public var result: SwiftResult
public var effectSpecifiers: [SwiftEffectSpecifier]
public var thrownTypedError: SwiftType?
public var genericParameters: [SwiftGenericParameterDeclaration]
public var genericRequirements: [SwiftGenericRequirement]

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

public var isTypedThrowing: Bool {
thrownTypedError != nil
}

/// Whether any parameter is variadic (`T...`).
public var hasVariadicParams: Bool {
parameters.contains(where: \.isVariadic)
Expand All @@ -53,13 +58,15 @@ public struct SwiftFunctionSignature: Equatable {
parameters: [SwiftParameter],
result: SwiftResult,
effectSpecifiers: [SwiftEffectSpecifier],
thrownTypedError: SwiftType? = nil,
genericParameters: [SwiftGenericParameterDeclaration],
genericRequirements: [SwiftGenericRequirement]
) {
self.selfParameter = selfParameter
self.parameters = parameters
self.result = result
self.effectSpecifiers = effectSpecifiers
self.thrownTypedError = thrownTypedError
self.genericParameters = genericParameters
self.genericRequirements = genericRequirements
}
Expand Down Expand Up @@ -120,7 +127,7 @@ extension SwiftFunctionSignature {
whereClause: node.genericWhereClause,
lookupContext: lookupContext
)
let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
let (parameters, effectSpecifiers, thrownTypedError) = try Self.translateFunctionSignature(
node.signature,
lookupContext: lookupContext
)
Expand All @@ -138,6 +145,7 @@ extension SwiftFunctionSignature {
parameters: parameters,
result: SwiftResult(convention: .direct, type: type),
effectSpecifiers: effectSpecifiers,
thrownTypedError: thrownTypedError,
genericParameters: genericParams,
genericRequirements: genericRequirements
)
Expand Down Expand Up @@ -205,8 +213,8 @@ extension SwiftFunctionSignature {
selfParameter = nil
}

// Translate the parameters.
let (parameters, effectSpecifiers) = try Self.translateFunctionSignature(
// Translate the function signature
let (parameters, effectSpecifiers, thrownTypedError) = try Self.translateFunctionSignature(
node.signature,
lookupContext: lookupContext
)
Expand All @@ -227,6 +235,7 @@ extension SwiftFunctionSignature {
parameters: parameters,
result: result,
effectSpecifiers: effectSpecifiers,
thrownTypedError: thrownTypedError,
genericParameters: genericParams,
genericRequirements: genericRequirements
)
Expand Down Expand Up @@ -289,11 +298,16 @@ extension SwiftFunctionSignature {
}

/// Translate the function signature, returning the list of translated
/// parameters and effect specifiers.
/// parameters, its effect specifiers, and the error type of a typed
/// `throws(E)` clause if present.
public static func translateFunctionSignature(
_ signature: FunctionSignatureSyntax,
lookupContext: SwiftTypeLookupContext
) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) {
) throws -> (
parameters: [SwiftParameter],
effectSpecifiers: [SwiftEffectSpecifier],
thrownTypedError: SwiftType?
) {
var effectSpecifiers = [SwiftEffectSpecifier]()
if signature.effectSpecifiers?.throwsClause != nil {
effectSpecifiers.append(.throws)
Expand All @@ -302,11 +316,16 @@ extension SwiftFunctionSignature {
effectSpecifiers.append(.async)
}

let thrownTypedError = SwiftType.thrownTypedError(
from: signature.effectSpecifiers?.throwsClause,
lookupContext: lookupContext
)

let parameters = try signature.parameterClause.parameters.map { param in
try SwiftParameter(param, lookupContext: lookupContext)
}

return (parameters, effectSpecifiers)
return (parameters, effectSpecifiers, thrownTypedError)
}

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

var effectSpecifiers: [SwiftEffectSpecifier]? = nil
var accessorEffects: AccessorEffects? = nil
switch binding.accessorBlock?.accessors {
case .getter(let getter):
if let getter = getter.as(AccessorDeclSyntax.self) {
effectSpecifiers = try Self.effectSpecifiers(from: getter)
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
}
case .accessors(let accessors):
if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
effectSpecifiers = try Self.effectSpecifiers(from: getter)
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
}
default:
break
}

self.effectSpecifiers = effectSpecifiers ?? []
self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? []
self.thrownTypedError = accessorEffects?.thrownTypedError

if isSet {
self.parameters = [
Expand Down Expand Up @@ -388,21 +408,22 @@ extension SwiftFunctionSignature {
return p
}

var effectSpecifiers: [SwiftEffectSpecifier]? = nil
var accessorEffects: AccessorEffects? = nil
switch subscriptNode.accessorBlock?.accessors {
case .getter(let getter):
if let getter = getter.as(AccessorDeclSyntax.self) {
effectSpecifiers = try Self.effectSpecifiers(from: getter)
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
}
case .accessors(let accessors):
if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) {
effectSpecifiers = try Self.effectSpecifiers(from: getter)
accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext)
}
default:
break
}

self.effectSpecifiers = effectSpecifiers ?? []
self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? []
self.thrownTypedError = accessorEffects?.thrownTypedError

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

private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] {
struct AccessorEffects {
var effectSpecifiers: [SwiftEffectSpecifier] = []
var thrownTypedError: SwiftType? = nil
}

private static func translateEffectSpecifiers(
from decl: AccessorDeclSyntax,
lookupContext: SwiftTypeLookupContext
) -> AccessorEffects {
var effectSpecifiers = [SwiftEffectSpecifier]()
if decl.effectSpecifiers?.throwsClause != nil {
effectSpecifiers.append(.throws)
}
if decl.effectSpecifiers?.asyncSpecifier != nil {
effectSpecifiers.append(.async)
}
return effectSpecifiers

let thrownTypedError = SwiftType.thrownTypedError(
from: decl.effectSpecifiers?.throwsClause,
lookupContext: lookupContext
)

return AccessorEffects(effectSpecifiers: effectSpecifiers, thrownTypedError: thrownTypedError)
}

private static func variableSelfParameter(
Expand Down
11 changes: 6 additions & 5 deletions Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public struct SwiftFunctionType: Equatable {

public var effectSpecifiers: [SwiftEffectSpecifier] = []

public var thrownTypedError: SwiftType? = nil

public var isAsync: Bool { effectSpecifiers.contains(.async) }

public var isThrowing: Bool { effectSpecifiers.contains(.throws) }
public var isTypedThrowing: Bool { thrownTypedError != nil }
public var thrownTypedError: SwiftType? = nil

public init(
convention: Convention,
Expand Down Expand Up @@ -95,9 +95,10 @@ extension SwiftFunctionType {
}
if let throwsClause = node.effectSpecifiers?.throwsClause {
effectSpecifiers.append(.throws)
if let errorTypeNode = throwsClause.type {
self.thrownTypedError = try? SwiftType(errorTypeNode, lookupContext: lookupContext)
}
self.thrownTypedError = SwiftType.thrownTypedError(
from: throwsClause,
lookupContext: lookupContext
)
}
self.effectSpecifiers = effectSpecifiers
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/SwiftExtract/SwiftTypes/SwiftType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,18 @@ extension SwiftNominalType {
}
}

extension SwiftType {
public static func thrownTypedError(
from throwsClause: ThrowsClauseSyntax?,
lookupContext: SwiftTypeLookupContext
) -> SwiftType? {
guard let errorTypeNode = throwsClause?.type else {
return nil
}
return try? SwiftType(errorTypeNode, lookupContext: lookupContext)
}
}

extension SwiftType {
public init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws {
var knownTypes: SwiftKnownTypes {
Expand Down
Loading
Loading