diff --git a/.gitignore b/.gitignore index 7a449f217..398fb98c6 100644 --- a/.gitignore +++ b/.gitignore @@ -58,7 +58,7 @@ Package.resolved .build/ *.o *.d -*.swiftdeps +*.swiftdeps* # CocoaPods # diff --git a/Examples/Podfile b/Examples/Podfile index 67fc87be6..3cfa86b14 100644 --- a/Examples/Podfile +++ b/Examples/Podfile @@ -3,7 +3,8 @@ IPHONEOS_DEPLOYMENT_TARGET = '14.0' GITHUB_REPOSITORY = ENV['GITHUB_REPOSITORY'] || 'SwiftyLab/MetaCodable' GITHUB_SERVER_URL = ENV['GITHUB_SERVER_URL'] || 'https://github.com' -METACODABLE_DEV_BRANCH = ENV['GITHUB_HEAD_REF'] || ENV['GITHUB_REF_NAME'] +GITHUB_HEAD_REF = ENV['GITHUB_HEAD_REF'] +METACODABLE_DEV_BRANCH = GITHUB_HEAD_REF&.empty? ? ENV['GITHUB_REF_NAME'] : GITHUB_HEAD_REF DEV_SOURCE = METACODABLE_DEV_BRANCH ? { :git => "#{GITHUB_SERVER_URL}/#{GITHUB_REPOSITORY}.git", :branch => METACODABLE_DEV_BRANCH } : { :path => "../" } def add_metacodable diff --git a/Package.swift b/Package.swift index 3dc521a6d..d87f74da3 100644 --- a/Package.swift +++ b/Package.swift @@ -31,6 +31,7 @@ let package = Package( name: "PluginCore", dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftDiagnostics", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), diff --git a/Package@swift-5.swift b/Package@swift-5.swift index dd3f1e75c..c6e48b455 100644 --- a/Package@swift-5.swift +++ b/Package@swift-5.swift @@ -30,6 +30,7 @@ let package = Package( name: "PluginCore", dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftDiagnostics", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), diff --git a/Sources/PluginCore/Diagnostics/MetaCodableMessage.swift b/Sources/PluginCore/Diagnostics/MetaCodableMessage.swift index c9f368d39..176910ddf 100644 --- a/Sources/PluginCore/Diagnostics/MetaCodableMessage.swift +++ b/Sources/PluginCore/Diagnostics/MetaCodableMessage.swift @@ -98,10 +98,11 @@ struct MetaCodableMessage: Error, DiagnosticMessage, FixItMessage { message: String, id: MessageID ) -> MetaCodableMessage { return .init( - macro: macro, - message: message, - messageID: id, - severity: .warning + macro: macro, message: message, messageID: id, severity: .warning ) } } + +#if !canImport(SwiftSyntax600) +extension MetaCodableMessage: @unchecked Sendable {} +#endif diff --git a/Sources/PluginCore/Expansion/AttributeExpander.swift b/Sources/PluginCore/Expansion/AttributeExpander.swift index 48e51cd15..b689d3ffc 100644 --- a/Sources/PluginCore/Expansion/AttributeExpander.swift +++ b/Sources/PluginCore/Expansion/AttributeExpander.swift @@ -64,11 +64,22 @@ struct AttributeExpander { let decodable = variable.protocol(named: dProtocol, in: protocols) let encodable = variable.protocol(named: eProtocol, in: protocols) - return [ + var extensions = [ decoding(type: type, conformingTo: decodable, in: context), encoding(type: type, conformingTo: encodable, in: context), codingKeys(for: type, confirmingTo: protocols, in: context), ].compactMap { $0 } + for index in extensions.indices { + // attach available attributes from original declaration + // to generated expanded declaration + extensions[index].attributes = AttributeListSyntax { + for attr in options.availableAttributes { + .attribute(attr) + } + extensions[index].attributes + } + } + return extensions } /// Provides the `Decodable` extension declaration. diff --git a/Sources/PluginCore/Expansion/Options/Options.swift b/Sources/PluginCore/Expansion/Options/Options.swift index 6cfbd9e89..5b5cad3b6 100644 --- a/Sources/PluginCore/Expansion/Options/Options.swift +++ b/Sources/PluginCore/Expansion/Options/Options.swift @@ -11,6 +11,9 @@ extension AttributeExpander { /// The list of modifiers generator for /// conformance implementation declarations. let modifiersGenerator: DeclModifiersGenerator + /// The list of `@available` attributes attached to + /// original expanded declaration. + let availableAttributes: [AttributeSyntax] /// Memberwise initialization generator with provided options. /// @@ -29,6 +32,17 @@ extension AttributeExpander { /// - Returns: The newly created options. init(for decl: some DeclGroupSyntax) { self.modifiersGenerator = .init(decl: decl) + self.availableAttributes = decl.attributes.compactMap { attribute in + switch attribute { + case .attribute(let attr): + guard + attr.attributeName.trimmed.description == "available" + else { fallthrough } + return attr + default: + return nil + } + } } } } diff --git a/Sources/PluginCore/Variables/Enum/Case/BasicEnumCaseVariable.swift b/Sources/PluginCore/Variables/Enum/Case/BasicEnumCaseVariable.swift index da21e1447..0f8494d3f 100644 --- a/Sources/PluginCore/Variables/Enum/Case/BasicEnumCaseVariable.swift +++ b/Sources/PluginCore/Variables/Enum/Case/BasicEnumCaseVariable.swift @@ -1,3 +1,4 @@ +import SwiftOperators import SwiftSyntax import SwiftSyntaxMacros @@ -117,7 +118,17 @@ struct BasicEnumCaseVariable: EnumCaseVariable { let label = SwitchCaseLabelSyntax( caseItems: .init { for value in location.values { - let pattern = ExpressionPatternSyntax(expression: value) + let expr = + OperatorTable.standardOperators + .foldAll(value) { _ in }.as(ExprSyntax.self) ?? value + let pattern = + if let asExpr = expr.as(AsExprSyntax.self) { + ExpressionPatternSyntax( + expression: asExpr.expression.trimmed + ) + } else { + ExpressionPatternSyntax(expression: expr) + } SwitchCaseItemSyntax(pattern: pattern) } } diff --git a/Sources/PluginCore/Variables/Enum/Switcher/InternallyTaggedEnumSwitcher.swift b/Sources/PluginCore/Variables/Enum/Switcher/InternallyTaggedEnumSwitcher.swift index 6959ff389..1511e3464 100644 --- a/Sources/PluginCore/Variables/Enum/Switcher/InternallyTaggedEnumSwitcher.swift +++ b/Sources/PluginCore/Variables/Enum/Switcher/InternallyTaggedEnumSwitcher.swift @@ -114,7 +114,10 @@ where Variable: PropertyVariable { let containerVariable = ContainerVariable( encodeContainer: encodeContainer, base: output.variable ) - node.register(variable: containerVariable, keyPath: keys) + node.register( + variable: containerVariable, keyPath: keys, + immutableEncodeContainer: true + ) self.node = node } diff --git a/Sources/PluginCore/Variables/Enum/Switcher/TaggedEnumSwitcherVariable.swift b/Sources/PluginCore/Variables/Enum/Switcher/TaggedEnumSwitcherVariable.swift index 96e36f3c6..d3c5f8d45 100644 --- a/Sources/PluginCore/Variables/Enum/Switcher/TaggedEnumSwitcherVariable.swift +++ b/Sources/PluginCore/Variables/Enum/Switcher/TaggedEnumSwitcherVariable.swift @@ -101,12 +101,17 @@ extension EnumSwitcherVariable { let label = SwitchCaseLabelSyntax { .init(pattern: pattern, whereClause: whereClause) } - let caseSyntax = CodeBlockItemListSyntax { - preSyntax("\(value.encodeExprs.first!)") - generated.code.combined() - } + + let generatedCode = generated.code.combined() SwitchCaseSyntax(label: .case(label)) { - !caseSyntax.isEmpty ? caseSyntax : "break" + if !generatedCode.isEmpty { + CodeBlockItemListSyntax { + preSyntax("\(value.encodeExprs.first!)") + generatedCode + } + } else { + "break" + } } } if `default` || !allEncodable || anyEncodeCondition { diff --git a/Sources/PluginCore/Variables/Enum/Switcher/UnTaggedEnumSwitcher.swift b/Sources/PluginCore/Variables/Enum/Switcher/UnTaggedEnumSwitcher.swift index 3c0f607e9..7c4029034 100644 --- a/Sources/PluginCore/Variables/Enum/Switcher/UnTaggedEnumSwitcher.swift +++ b/Sources/PluginCore/Variables/Enum/Switcher/UnTaggedEnumSwitcher.swift @@ -85,15 +85,18 @@ struct UnTaggedEnumSwitcher: EnumSwitcherVariable { from location: EnumSwitcherLocation ) -> CodeBlockItemListSyntax { var cases = location.cases + let result = decodingSyntax(for: &cases, from: location, in: context) return CodeBlockItemListSyntax { - """ - let context = DecodingError.Context( - codingPath: \(location.coder).codingPath, - debugDescription: "Couldn't decode any case." - ) - """ - "let \(error) = DecodingError.typeMismatch(Self.self, context)" - decodingSyntax(for: &cases, from: location, in: context) + if result.usesError { + """ + let context = DecodingError.Context( + codingPath: \(location.coder).codingPath, + debugDescription: "Couldn't decode any case." + ) + """ + "let \(error) = DecodingError.typeMismatch(Self.self, context)" + } + result.syntax } } @@ -107,32 +110,54 @@ struct UnTaggedEnumSwitcher: EnumSwitcherVariable { /// - location: The decoding location. /// - context: The context in which to perform the macro expansion. /// - /// - Returns: The generated decoding syntax. + /// - Returns: Whether `error` value is used and generated decoding syntax. private func decodingSyntax( for cases: inout [EnumVariable.Case], from location: EnumSwitcherLocation, in context: some MacroExpansionContext - ) -> CodeBlockItemListSyntax { - guard !cases.isEmpty else { return "throw \(error)" } + ) -> (usesError: Bool, syntax: CodeBlockItemListSyntax) { + guard !cases.isEmpty else { return (true, "throw \(error)") } + let `case` = cases.removeFirst() let coder = location.coder let cLocation = EnumCaseCodingLocation(coder: coder, values: []) let generated = `case`.variable.decoding(in: context, from: cLocation) + let nResult = decodingSyntax(for: &cases, from: location, in: context) let catchClauses = CatchClauseListSyntax { CatchClauseSyntax { - decodingSyntax(for: &cases, from: location, in: context) + nResult.syntax } } - return CodeBlockItemListSyntax { - let `case` = `case`.variable + + let eCase = `case`.variable + let tVisitor = ThrowingSyntaxVisitor(viewMode: .sourceAccurate) + let doBlock = CodeBlockItemListSyntax { + generated.code.codingSyntax + generated.code.conditionalSyntax + location.codeExpr(eCase.name, eCase.variables) + "return" + } + + tVisitor.walk(doBlock) + let nUsesError = tVisitor.throws && nResult.usesError + let eVisitor = ErrorUsageSyntaxVisitor( + error: error, usesProvidedError: nUsesError, + viewMode: .sourceAccurate + ) + eVisitor.walk(doBlock) + + let syntax = CodeBlockItemListSyntax { generated.code.containerSyntax - DoStmtSyntax(catchClauses: catchClauses) { - generated.code.codingSyntax - generated.code.conditionalSyntax - location.codeExpr(`case`.name, `case`.variables) - "return" + if tVisitor.throws { + DoStmtSyntax(catchClauses: catchClauses) { + doBlock + } + } else { + doBlock } } + + return (eVisitor.usesProvidedError || nUsesError, syntax) } /// Provides the syntax for encoding at the provided location. @@ -169,3 +194,181 @@ struct UnTaggedEnumSwitcher: EnumSwitcherVariable { return [] } } + +fileprivate extension UnTaggedEnumSwitcher { + /// A `SyntaxVisitor` that checks provided syntax throwing behaviour. + /// + /// This `SyntaxVisitor` checks whether syntax has any un-caught error. + final class ThrowingSyntaxVisitor: SyntaxVisitor { + /// Whether the syntax has un-handled errors. + private(set) var `throws` = false + + /// Decides whether to visit or skip children of provided node. + /// + /// If any unhandled error is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + func visit(node: S) -> SyntaxVisitorContinueKind { + guard !`throws` else { return .skipChildren } + return .visitChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// Sets un-handled error status to `true`. + /// + /// - Parameter n: The node to visit. + /// - Returns: To skip visiting children of node. + override func visit(_ n: TryExprSyntax) -> SyntaxVisitorContinueKind { + `throws` = true + return .skipChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// Sets un-handled error status to `true`. + /// + /// - Parameter n: The node to visit. + /// - Returns: To skip visiting children of node. + override func visit(_ n: ThrowStmtSyntax) -> SyntaxVisitorContinueKind { + `throws` = true + return .skipChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// Skips visiting children nodes: + /// * If any unhandled error is already detected. + /// * If node is inside a `do` statement. + /// + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockSyntax + ) -> SyntaxVisitorContinueKind { + guard !`throws` else { return .skipChildren } + return node.parent?.kind == .doStmt ? .skipChildren : .visitChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If any unhandled error is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockItemListSyntax + ) -> SyntaxVisitorContinueKind { + return self.visit(node: node) + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If any unhandled error is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockItemSyntax + ) -> SyntaxVisitorContinueKind { + return self.visit(node: node) + } + } + + /// A `SyntaxVisitor` that checks provided syntax error usage. + /// + /// This `SyntaxVisitor` checks whether syntax uses provided error value. + final class ErrorUsageSyntaxVisitor: SyntaxVisitor { + /// The error variable usage is checked of. + let error: TokenSyntax + /// Whether any usage is detected. + private(set) var usesProvidedError = false + + /// Creates a new visitor with provided parameters. + /// + /// - Parameters: + /// - error: The error variable usage is checked of. + /// - usesProvidedError: Whether any usage is detected already. + /// - viewMode: The visit mode when traversing syntax tree. + init( + error: TokenSyntax, usesProvidedError: Bool, + viewMode: SyntaxTreeViewMode = .sourceAccurate + ) { + self.error = error + self.usesProvidedError = usesProvidedError + super.init(viewMode: viewMode) + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If error usage is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + func visit(node: S) -> SyntaxVisitorContinueKind { + guard !usesProvidedError else { return .skipChildren } + return .visitChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// Updates error usage status if uses in the throwing statement. + /// + /// - Parameter node: The node to visit. + /// - Returns: To skip visiting children of node. + override func visit( + _ node: ThrowStmtSyntax + ) -> SyntaxVisitorContinueKind { + usesProvidedError = + usesProvidedError + || node.expression.trimmed.description == error.trimmed.text + return .skipChildren + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If error usage is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockSyntax + ) -> SyntaxVisitorContinueKind { + return self.visit(node: node) + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If error usage is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockItemListSyntax + ) -> SyntaxVisitorContinueKind { + return self.visit(node: node) + } + + /// Decides whether to visit or skip children of provided node. + /// + /// If error usage is already detected, visiting is skipped. + /// Otherwise children are visited. + /// + /// - Parameter node: The node to visit. + /// - Returns: Whether to visit or skip children of node. + override func visit( + _ node: CodeBlockItemSyntax + ) -> SyntaxVisitorContinueKind { + return self.visit(node: node) + } + } +} diff --git a/Sources/PluginCore/Variables/Property/Data/DecodingFallback.swift b/Sources/PluginCore/Variables/Property/Data/DecodingFallback.swift index 9088b080d..f702e05ec 100644 --- a/Sources/PluginCore/Variables/Property/Data/DecodingFallback.swift +++ b/Sources/PluginCore/Variables/Property/Data/DecodingFallback.swift @@ -37,6 +37,9 @@ package enum DecodingFallback { /// /// - Parameters: /// - location: The decoding location to decode from. + /// - nestedContainer: The nested container name to use if present. + /// - nestedContainerHasVariables: Whether nested container has + /// some variables as direct children. /// - decoding: The nested container decoding /// code block generator. /// @@ -44,18 +47,23 @@ package enum DecodingFallback { func represented( location: PropertyVariableTreeNode.CodingLocation, nestedContainer: TokenSyntax?, + nestedContainerHasVariables: Bool, nestedDecoding decoding: (Container) -> Generated ) -> Generated { return switch location { case .coder(let coder, let kType): represented( decoder: coder, keyType: kType, - nestedContainer: nestedContainer, nestedDecoding: decoding + nestedContainer: nestedContainer, + nestedContainerHasVariables: nestedContainerHasVariables, + nestedDecoding: decoding ) case .container(let container, let key): represented( decodingContainer: container, fromKey: key, - nestedContainer: nestedContainer, nestedDecoding: decoding + nestedContainer: nestedContainer, + nestedContainerHasVariables: nestedContainerHasVariables, + nestedDecoding: decoding ) } } @@ -66,6 +74,9 @@ package enum DecodingFallback { /// - Parameters: /// - decoder: The decoder to decode from. /// - type: The decoder container `CodingKey` type. + /// - nestedContainer: The nested container name to use if present. + /// - nestedContainerHasVariables: Whether nested container has + /// some variables as direct children. /// - decoding: The nested container decoding /// code block generator. /// @@ -74,6 +85,7 @@ package enum DecodingFallback { decoder: TokenSyntax, keyType type: ExprSyntax, nestedContainer: TokenSyntax?, + nestedContainerHasVariables: Bool, nestedDecoding decoding: (Container) -> Generated ) -> Generated { let isOptional: Bool @@ -86,6 +98,7 @@ package enum DecodingFallback { isOptional = false fallbacks = [] } + let cToken = nestedContainer ?? "container" let container = Container(name: cToken, isOptional: isOptional) let generated = decoding(container) @@ -98,11 +111,13 @@ package enum DecodingFallback { } generated.containerSyntax } + + let cBinding = nestedContainerHasVariables ? container.name : "_" let conditionalSyntax = CodeBlockItemListSyntax { if isOptional { try! IfExprSyntax( """ - if let \(container.name) = \(container.name) + if let \(cBinding) = \(container.name) """ ) { generated.conditionalSyntax @@ -126,6 +141,9 @@ package enum DecodingFallback { /// - Parameters: /// - container: The container to decode from. /// - key: The key from where to decode. + /// - nestedContainer: The nested container name to use if present. + /// - nestedContainerHasVariables: Whether nested container has + /// some variables as direct children. /// - decoding: The nested container decoding /// code block generator. /// @@ -134,9 +152,11 @@ package enum DecodingFallback { decodingContainer container: Container, fromKey key: CodingKeysMap.Key, nestedContainer: TokenSyntax?, + nestedContainerHasVariables: Bool, nestedDecoding decoding: (Container) -> Generated ) -> Generated { let nContainer = nestedContainer ?? "\(key.raw)_\(container.name)" + let nContainerBinding = nestedContainerHasVariables ? nContainer : "_" let containerSyntax: CodeBlockItemListSyntax let codingSyntax: CodeBlockItemListSyntax let conditionalSyntax: CodeBlockItemListSyntax @@ -169,7 +189,7 @@ package enum DecodingFallback { conditionalSyntax = CodeBlockItemListSyntax { try! IfExprSyntax( """ - if let \(nContainer) = \(nContainer) + if let \(nContainerBinding) = \(nContainer) """ ) { generated.conditionalSyntax @@ -203,7 +223,7 @@ package enum DecodingFallback { } conditionalSyntax = CodeBlockItemListSyntax { try! IfExprSyntax( - "if let \(nContainer) = \(nContainer)", + "if let \(nContainerBinding) = \(nContainer)", bodyBuilder: { generated.conditionalSyntax }, diff --git a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift index 49117ae6f..9a5dce88a 100644 --- a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift +++ b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift @@ -24,6 +24,12 @@ final class PropertyVariableTreeNode: Variable { /// This is used for caching the container variable name to be reused, /// allowing not to retrieve container repeatedly. private var decodingContainer: TokenSyntax? + /// Whether the encoding container variable linked to this node + /// should be declared as immutable. + /// + /// This is used to suppress mutability warning in case of + /// internally tagged enums. + private var immutableEncodeContainer: Bool = false /// List of all the linked variables registered. /// @@ -60,9 +66,12 @@ final class PropertyVariableTreeNode: Variable { /// additional macro metadata. /// - keyPath: The `CodingKey` path where the value /// will be decode/encoded. + /// - immutableEncodeContainer: Whether the encoding container variable + /// direct parent of `variable` should be declared as immutable. func register( variable: any PropertyVariable, - keyPath: [CodingKeysMap.Key] + keyPath: [CodingKeysMap.Key], + immutableEncodeContainer: Bool = false ) { guard !keyPath.isEmpty else { variables.append(variable); return } @@ -71,9 +80,18 @@ final class PropertyVariableTreeNode: Variable { children[key] = .init(variables: [], children: [:]) } + if keyPath.count == 1 { + precondition( + !immutableEncodeContainer + || linkedVariables.filter { $0.encode ?? true }.isEmpty + ) + self.immutableEncodeContainer = immutableEncodeContainer + } + children[key]!.register( variable: variable, - keyPath: Array(keyPath.dropFirst()) + keyPath: Array(keyPath.dropFirst()), + immutableEncodeContainer: immutableEncodeContainer ) } } @@ -122,7 +140,10 @@ extension PropertyVariableTreeNode { .map(\.decodingFallback) .reduce(.ifMissing([], ifError: []), +) .represented( - location: location, nestedContainer: self.decodingContainer + location: location, nestedContainer: self.decodingContainer, + nestedContainerHasVariables: !self.children.lazy + .flatMap(\.value.variables) + .filter { $0.decode ?? true }.isEmpty ) { container in self.decodingContainer = container.name let generated = decodableChildren.map { cKey, node in @@ -182,6 +203,7 @@ extension PropertyVariableTreeNode { in context: some MacroExpansionContext, to location: CodingLocation ) -> Generated { + let specifier: TokenSyntax = immutableEncodeContainer ? "let" : "var" let syntax = CodeBlockItemListSyntax { for variable in data?.variables ?? variables where variable.encode ?? true { @@ -201,7 +223,7 @@ extension PropertyVariableTreeNode { case .coder(let encoder, let type): let container: TokenSyntax = "container" """ - var container = \(encoder).container(keyedBy: \(type)) + \(specifier) \(container) = \(encoder).container(keyedBy: \(type)) """ for (cKey, node) in children where data?.hasKey(cKey) ?? true { @@ -218,7 +240,7 @@ extension PropertyVariableTreeNode { let nestedContainer: TokenSyntax = "\(key.raw)_\(container.name)" """ - var \(nestedContainer) = \(container.name).nestedContainer(keyedBy: \(key.type), forKey: \(key.expr)) + \(specifier) \(nestedContainer) = \(container.name).nestedContainer(keyedBy: \(key.type), forKey: \(key.expr)) """ for (cKey, node) in children where data?.hasKey(cKey) ?? true { diff --git a/Sources/PluginCore/Variables/Type/ActorVariable.swift b/Sources/PluginCore/Variables/Type/ActorVariable.swift index 3bc3e6c9d..81419d990 100644 --- a/Sources/PluginCore/Variables/Type/ActorVariable.swift +++ b/Sources/PluginCore/Variables/Type/ActorVariable.swift @@ -50,11 +50,18 @@ struct ActorVariable: TypeVariable, DeclaredVariable, ComposedVariable, var inheritanceClause = generated.inheritanceClause let types = inheritanceClause?.inheritedTypes ?? [] inheritedTypes: for (index, var inheritedType) in types.enumerated() { + #if canImport(SwiftSyntax600) + let fallbackType = AttributedTypeSyntax( + specifiers: [], baseType: inheritedType.type + ) + #else + let fallbackType = AttributedTypeSyntax( + baseType: inheritedType.type + ) + #endif + var type = - inheritedType.type.as(AttributedTypeSyntax.self) - ?? AttributedTypeSyntax( - specifiers: [], baseType: inheritedType.type - ) + inheritedType.type.as(AttributedTypeSyntax.self) ?? fallbackType let attribute = type.attributes.first { attribute in return switch attribute { case .attribute(let attr): @@ -66,7 +73,14 @@ struct ActorVariable: TypeVariable, DeclaredVariable, ComposedVariable, } guard attribute == nil else { continue inheritedTypes } + #if canImport(SwiftSyntax510) let index = inheritanceClause!.inheritedTypes.index(at: index) + #else + let inheritedTypes = inheritanceClause!.inheritedTypes + let index = inheritedTypes.index( + inheritedTypes.startIndex, offsetBy: index + ) + #endif type.attributes.append(.attribute(preconcurrency)) inheritedType.type = .init(type) inheritanceClause!.inheritedTypes[index] = inheritedType diff --git a/Sources/PluginCore/Variables/Type/TypeVariable.swift b/Sources/PluginCore/Variables/Type/TypeVariable.swift index 47cd76416..1d0c03fb7 100644 --- a/Sources/PluginCore/Variables/Type/TypeVariable.swift +++ b/Sources/PluginCore/Variables/Type/TypeVariable.swift @@ -163,3 +163,7 @@ package struct TypeCodingLocation { self.conformance = conformance } } + +#if !canImport(SwiftSyntax600) +extension TypeCodingLocation.Method: @unchecked Sendable {} +#endif diff --git a/Tests/MetaCodableTests/CodableTests.swift b/Tests/MetaCodableTests/CodableTests.swift index c4288288c..ded54c9b9 100644 --- a/Tests/MetaCodableTests/CodableTests.swift +++ b/Tests/MetaCodableTests/CodableTests.swift @@ -14,6 +14,69 @@ import SwiftSyntaxMacrosTestSupport #endif struct CodableTests { + struct WithoutAvailableAttribute { + @Codable + @available(*, deprecated, message: "Deprecated") + struct SomeCodable { + let value: String + static let other: String = "other" + public private(set) static var otherM: String { + get { "otherM" } + set { Issue.record("Invalid setter invocation") } + } + } + + @Test + func expansion() throws { + assertMacroExpansion( + """ + @Codable + @available(*, deprecated, message: "Deprecated") + struct SomeCodable { + let value: String + static let other: String = "other" + public private(set) static var otherM: String { + get { "otherM" } + set { Issue.record("Invalid setter invocation") } + } + } + """, + expandedSource: + """ + @available(*, deprecated, message: "Deprecated") + struct SomeCodable { + let value: String + static let other: String = "other" + public private(set) static var otherM: String { + get { "otherM" } + set { Issue.record("Invalid setter invocation") } + } + } + + @available(*, deprecated, message: "Deprecated") extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decode(String.self, forKey: CodingKeys.value) + } + } + + @available(*, deprecated, message: "Deprecated") extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.value, forKey: CodingKeys.value) + } + } + + @available(*, deprecated, message: "Deprecated") extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + } + } + """ + ) + } + } + struct WithoutAnyCustomization { @Codable struct SomeCodable { diff --git a/Tests/MetaCodableTests/CodedAs/CodedAsEnumTests.swift b/Tests/MetaCodableTests/CodedAs/CodedAsEnumTests.swift index 7095823f4..51c902e32 100644 --- a/Tests/MetaCodableTests/CodedAs/CodedAsEnumTests.swift +++ b/Tests/MetaCodableTests/CodedAs/CodedAsEnumTests.swift @@ -248,7 +248,7 @@ struct CodedAsEnumTests { extension Command: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .load(key: let key): @@ -493,7 +493,7 @@ struct CodedAsEnumTests { extension SomeEnum: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .bool(_: let variable): diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift index b1a95c966..4e73656b8 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift @@ -399,8 +399,8 @@ struct CodedAtDefaultChoiceTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -496,8 +496,8 @@ struct CodedAtDefaultChoiceTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -592,8 +592,8 @@ struct CodedAtDefaultChoiceTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift index 16c083839..864351584 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift @@ -347,7 +347,7 @@ struct CodedAtDefaultOnlyMissingTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" } else { @@ -416,7 +416,7 @@ struct CodedAtDefaultOnlyMissingTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" } else { @@ -484,7 +484,7 @@ struct CodedAtDefaultOnlyMissingTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" } else { diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift index a023179a1..dd3d80ae5 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift @@ -399,8 +399,8 @@ struct CodedAtDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -496,8 +496,8 @@ struct CodedAtDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -592,8 +592,8 @@ struct CodedAtDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift index bf0eba31c..3e6f5b531 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift @@ -105,7 +105,7 @@ struct CodedAtEnumTests { extension SomeEnum: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .bool(_: let variable): @@ -216,7 +216,7 @@ struct CodedAtEnumTests { extension Command: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .load(key: let key): @@ -306,7 +306,7 @@ struct CodedAtEnumTests { extension Command: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .load(key: let key): @@ -398,7 +398,7 @@ struct CodedAtEnumTests { extension Command: Encodable { func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + let container = encoder.container(keyedBy: CodingKeys.self) var typeContainer = container switch self { case .load(key: let key): @@ -570,7 +570,7 @@ struct CodedAtEnumTests { let container = try decoder.container(keyedBy: CodingKeys.self) let data_container = ((try? container.decodeNil(forKey: CodingKeys.data)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.data) : nil let attributes_data_container = ((try? data_container?.decodeNil(forKey: CodingKeys.attributes)) == false) ? try data_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.attributes) : nil - if let data_container = data_container { + if let _ = data_container { if let attributes_data_container = attributes_data_container { type = try attributes_data_container.decodeIfPresent(String.self, forKey: CodingKeys.type) } else { @@ -584,7 +584,7 @@ struct CodedAtEnumTests { let _0: Registration _0 = try Registration(from: decoder) self = .registration(_0) - case nil as String?: + case nil: let _0: Expiry _0 = try Expiry(from: decoder) self = .expiry(_0) @@ -602,7 +602,7 @@ struct CodedAtEnumTests { func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) var data_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.data) - var attributes_data_container = data_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.attributes) + let attributes_data_container = data_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.attributes) var typeContainer = attributes_data_container switch self { case .registration(let _0): diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtHelperDefaultTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtHelperDefaultTests.swift index eb8b8c2aa..3e4c921c9 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtHelperDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtHelperDefaultTests.swift @@ -306,8 +306,8 @@ struct CodedAtHelperDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] @@ -407,8 +407,8 @@ struct CodedAtHelperDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtHelperTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtHelperTests.swift index 93acb9d52..7124fe50a 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtHelperTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtHelperTests.swift @@ -319,7 +319,7 @@ struct CodedAtHelperTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) } else { diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift index d4801fb54..4203b557f 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift @@ -551,7 +551,7 @@ struct CodedAtTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) } else { @@ -617,7 +617,7 @@ struct CodedAtTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) } else { diff --git a/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift index 8266f325c..c5e1b546b 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift @@ -221,7 +221,7 @@ struct CodedInDefaultTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -301,7 +301,7 @@ struct CodedInDefaultTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -335,73 +335,86 @@ struct CodedInDefaultTests { } """ ) + } - assertMacroExpansion( - """ - @Codable - @MemberInit - struct SomeCodable { - @Default("some") - @CodedIn("nested") - let value: String! - } - """, - expandedSource: + struct ForcedUnwrap { + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedIn("nested") + let value: String! + } + + @Test + func expansion() throws { + assertMacroExpansion( """ + @Codable + @MemberInit struct SomeCodable { + @Default("some") + @CodedIn("nested") let value: String! - - init(value: String! = "some") { - self.value = value - } } - - extension SomeCodable: Decodable { - init(from decoder: any Decoder) throws { - let container = try? decoder.container(keyedBy: CodingKeys.self) - let nested_container: KeyedDecodingContainer? - let nested_containerMissing: Bool - if (try? container?.decodeNil(forKey: CodingKeys.nested)) == false { - nested_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - nested_containerMissing = false - } else { - nested_container = nil - nested_containerMissing = true + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value } - if let container = container { - if let nested_container = nested_container { - do { - self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" - } catch { + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let nested_container: KeyedDecodingContainer? + let nested_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_containerMissing = false + } else { + nested_container = nil + nested_containerMissing = true + } + if let _ = container { + if let nested_container = nested_container { + do { + self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else if nested_containerMissing { + self.value = "some" + } else { self.value = "some" } - } else if nested_containerMissing { - self.value = "some" } else { self.value = "some" } - } else { - self.value = "some" } } - } - extension SomeCodable: Encodable { - func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var nested_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - try nested_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var nested_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } } - } - extension SomeCodable { - enum CodingKeys: String, CodingKey { - case value = "value" - case nested = "nested" + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case nested = "nested" + } } - } - """ - ) + """ + ) + } } } @@ -457,8 +470,8 @@ struct CodedInDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -554,8 +567,8 @@ struct CodedInDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" @@ -596,90 +609,103 @@ struct CodedInDefaultTests { } """ ) + } - assertMacroExpansion( - """ - @Codable - @MemberInit - struct SomeCodable { - @Default("some") - @CodedIn("deeply", "nested") - let value: String! - } - """, - expandedSource: + struct ForceUnwrap { + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedIn("deeply", "nested") + let value: String! + } + + @Test + func expansion() throws { + assertMacroExpansion( """ + @Codable + @MemberInit struct SomeCodable { + @Default("some") + @CodedIn("deeply", "nested") let value: String! - - init(value: String! = "some") { - self.value = value - } } - - extension SomeCodable: Decodable { - init(from decoder: any Decoder) throws { - let container = try? decoder.container(keyedBy: CodingKeys.self) - let deeply_container: KeyedDecodingContainer? - let deeply_containerMissing: Bool - if (try? container?.decodeNil(forKey: CodingKeys.deeply)) == false { - deeply_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) - deeply_containerMissing = false - } else { - deeply_container = nil - deeply_containerMissing = true + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value } - let nested_deeply_container: KeyedDecodingContainer? - let nested_deeply_containerMissing: Bool - if (try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false { - nested_deeply_container = try? deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - nested_deeply_containerMissing = false - } else { - nested_deeply_container = nil - nested_deeply_containerMissing = true - } - if let container = container { - if let deeply_container = deeply_container { - if let nested_deeply_container = nested_deeply_container { - do { - self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" - } catch { + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let deeply_container: KeyedDecodingContainer? + let deeply_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.deeply)) == false { + deeply_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + deeply_containerMissing = false + } else { + deeply_container = nil + deeply_containerMissing = true + } + let nested_deeply_container: KeyedDecodingContainer? + let nested_deeply_containerMissing: Bool + if (try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_deeply_container = try? deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_deeply_containerMissing = false + } else { + nested_deeply_container = nil + nested_deeply_containerMissing = true + } + if let _ = container { + if let _ = deeply_container { + if let nested_deeply_container = nested_deeply_container { + do { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else if nested_deeply_containerMissing { + self.value = "some" + } else { self.value = "some" } - } else if nested_deeply_containerMissing { + } else if deeply_containerMissing { self.value = "some" } else { self.value = "some" } - } else if deeply_containerMissing { - self.value = "some" } else { self.value = "some" } - } else { - self.value = "some" } } - } - extension SomeCodable: Encodable { - func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) - var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } } - } - extension SomeCodable { - enum CodingKeys: String, CodingKey { - case value = "value" - case deeply = "deeply" - case nested = "nested" + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case deeply = "deeply" + case nested = "nested" + } } - } - """ - ) + """ + ) + } } } @@ -885,7 +911,7 @@ struct CodedInDefaultTests { if let value6_container = value6_container { self.value4 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) self.value5 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) - if let value4_value6_container = value4_value6_container { + if let _ = value4_value6_container { if let level_value4_value6_container = level_value4_value6_container { do { self.value1 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -1038,7 +1064,7 @@ struct CodedInDefaultTests { if let value6_container = value6_container { self.value4 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) self.value5 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) - if let value4_value6_container = value4_value6_container { + if let _ = value4_value6_container { if let level_value4_value6_container = level_value4_value6_container { do { self.value1 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" diff --git a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultChoiceTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultChoiceTests.swift index 5196f03ae..b7a48f428 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultChoiceTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultChoiceTests.swift @@ -184,7 +184,7 @@ struct CodedInHelperDefaultChoiceTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_container, forKey: CodingKeys.value) ?? ["some"] @@ -268,7 +268,7 @@ struct CodedInHelperDefaultChoiceTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_container, forKey: CodingKeys.value) ?? ["some"] @@ -361,8 +361,8 @@ struct CodedInHelperDefaultChoiceTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] @@ -462,8 +462,8 @@ struct CodedInHelperDefaultChoiceTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] diff --git a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultOnlyMissingTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultOnlyMissingTests.swift index 1c1cc9185..5f10a5e31 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultOnlyMissingTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultOnlyMissingTests.swift @@ -293,7 +293,7 @@ struct CodedInHelperDefaultOnlyMissingTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] } else { @@ -366,7 +366,7 @@ struct CodedInHelperDefaultOnlyMissingTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] } else { diff --git a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultTests.swift index e62a91ead..77ce09c53 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInHelperDefaultTests.swift @@ -184,7 +184,7 @@ struct CodedInHelperDefaultTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_container, forKey: CodingKeys.value) ?? ["some"] @@ -268,7 +268,7 @@ struct CodedInHelperDefaultTests { nested_container = nil nested_containerMissing = true } - if let container = container { + if let _ = container { if let nested_container = nested_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_container, forKey: CodingKeys.value) ?? ["some"] @@ -361,8 +361,8 @@ struct CodedInHelperDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] @@ -462,8 +462,8 @@ struct CodedInHelperDefaultTests { nested_deeply_container = nil nested_deeply_containerMissing = true } - if let container = container { - if let deeply_container = deeply_container { + if let _ = container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { do { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"] diff --git a/Tests/MetaCodableTests/CodedIn/CodedInHelperTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInHelperTests.swift index 33e38db41..16ac21ba8 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInHelperTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInHelperTests.swift @@ -342,7 +342,7 @@ struct CodedInHelperTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try SequenceCoder(output: [String].self, configuration: .lossy).decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) } else { diff --git a/Tests/MetaCodableTests/CodedIn/CodedInTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInTests.swift index 99278149f..05851bbb8 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInTests.swift @@ -507,7 +507,7 @@ struct CodedInTests { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) } else { @@ -537,61 +537,73 @@ struct CodedInTests { } """ ) + } - assertMacroExpansion( - """ - @Codable - @MemberInit - struct SomeCodable { - @CodedIn("deeply", "nested") - let value: String! - } - """, - expandedSource: + struct ForcedUnwrap { + @Codable + @MemberInit + struct SomeCodable { + @CodedIn("deeply", "nested") + let value: String! + } + + @Test + func expansion() throws { + assertMacroExpansion( """ + @Codable + @MemberInit struct SomeCodable { + @CodedIn("deeply", "nested") let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! - init(value: String! = nil) { - self.value = value + init(value: String! = nil) { + self.value = value + } } - } - extension SomeCodable: Decodable { - init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil - let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - if let deeply_container = deeply_container { - if let nested_deeply_container = nested_deeply_container { - self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil + let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil + if let _ = deeply_container { + if let nested_deeply_container = nested_deeply_container { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } else { + self.value = nil + } } else { self.value = nil } - } else { - self.value = nil } } - } - extension SomeCodable: Encodable { - func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) - var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } } - } - extension SomeCodable { - enum CodingKeys: String, CodingKey { - case value = "value" - case deeply = "deeply" - case nested = "nested" + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case deeply = "deeply" + case nested = "nested" + } } - } - """ - ) + """ + ) + } } } @@ -643,7 +655,7 @@ struct CodedInTests { let nested1_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested1)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested1) : nil let nested2_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested2)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) : nil let deeply1_container = ((try? container.decodeNil(forKey: CodingKeys.deeply1)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply1) : nil - if let deeply_container = deeply_container { + if let _ = deeply_container { if let nested1_deeply_container = nested1_deeply_container { self.value1 = try nested1_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) } else { diff --git a/Tests/MetaCodableTests/IgnoreInitializedTests.swift b/Tests/MetaCodableTests/IgnoreInitializedTests.swift index 54d69d566..86bb0992a 100644 --- a/Tests/MetaCodableTests/IgnoreInitializedTests.swift +++ b/Tests/MetaCodableTests/IgnoreInitializedTests.swift @@ -185,9 +185,9 @@ struct IgnoreInitializedTests { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .bool(_: _): - let contentEncoder = container.superEncoder(forKey: CodingKeys.bool) + break case .int(val: _): - let contentEncoder = container.superEncoder(forKey: CodingKeys.int) + break case .string(let _0): let contentEncoder = container.superEncoder(forKey: CodingKeys.string) try _0.encode(to: contentEncoder) @@ -297,9 +297,9 @@ struct IgnoreInitializedTests { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .bool(_: _): - let contentEncoder = container.superEncoder(forKey: CodingKeys.bool) + break case .int(val: _): - let contentEncoder = container.superEncoder(forKey: CodingKeys.int) + break case .string(let _0): let contentEncoder = container.superEncoder(forKey: CodingKeys.string) try _0.encode(to: contentEncoder) diff --git a/Tests/MetaCodableTests/UntaggedEnumTests.swift b/Tests/MetaCodableTests/UntaggedEnumTests.swift index 08a1c5185..3c3ed63e6 100644 --- a/Tests/MetaCodableTests/UntaggedEnumTests.swift +++ b/Tests/MetaCodableTests/UntaggedEnumTests.swift @@ -423,11 +423,6 @@ struct UntaggedEnumTests { extension CodableValue: Decodable { init(from decoder: any Decoder) throws { - let context = DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Couldn't decode any case." - ) - let __macro_local_13decodingErrorfMu0_ = DecodingError.typeMismatch(Self.self, context) let _0: Bool do { _0 = try Bool(from: decoder) @@ -476,12 +471,8 @@ struct UntaggedEnumTests { self = .dictionary(_0) return } catch { - do { - self = .`nil` - return - } catch { - throw __macro_local_13decodingErrorfMu0_ - } + self = .`nil` + return } } } @@ -520,6 +511,19 @@ struct UntaggedEnumTests { """ ) } + + @Test + func decoding() throws { + let data = try JSONDecoder().decode( + CodableValue.self, from: heterogenousJSONData + ) + switch data { + case .array(let values): + #expect(values.count == 7) + default: + Issue.record("Invalid data decoded") + } + } } struct NestedDecoding { @@ -664,32 +668,6 @@ struct UntaggedEnumTests { ) } } - - @Test - func unTaggedEnumDecoding() throws { - let data = try JSONDecoder().decode( - CodableValue.self, from: heterogenousJSONData - ) - switch data { - case .array(let values): - #expect(values.count == 7) - default: - Issue.record("Invalid data decoded") - } - } -} - -@Codable -@UnTagged -enum CodableValue { - case bool(Bool) - case uint(UInt) - case int(Int) - case float(Float) - case double(Double) - case string(String) - case array([Self]) - case dictionary([String: Self]) } let heterogenousJSONData = """