Trying to drum up some interest in a bug that keeps getting in my way when doing Macro Schtuff™. Essentially, it seems that although a macro can create a static function, it can't then be called , or used as a witness of a protocol?
I'm aware of at least 5 bug reports in this area, only one of which has even been triaged:
opened 10:03AM - 14 Sep 23 UTC
bug
swift macro
**Description**
When I try to access a static property (function) from another … file, I get an error: "Type 'TypeName' has no member 'member'".
**Steps to reproduce**
1. Create a macro that generates a static property or method
2. Attach the macro to the model
3. Access a static method or property from another file
**Expected behavior**
The code compiles without errors.
<img width="336" alt="Снимок экрана 2023-09-14 в 12 49 35" src="https://github.com/apple/swift/assets/54406512/60210d6c-cc6d-4aac-a5ac-ee33c529ce60">
<img width="619" alt="Снимок экрана 2023-09-14 в 12 49 55" src="https://github.com/apple/swift/assets/54406512/fd2145a9-9eef-4a0d-bbc2-537d7f216d6f">
**Environment**
- Swift compiler version info swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) Target: x86_64-apple-macosx13.0
- Xcode version info: Xcode 15.0 Build version 15A240d
- Deployment target: iOS 17.0
**Additional context**
Swift forum [topic](https://forums.swift.org/t/the-static-method-generated-by-extensionmacro-is-not-visible-to-compiler/67075)
A [demo](https://github.com/red-beeard/demo-macro) that reproduces this problem
opened 10:16PM - 21 Sep 23 UTC
bug
triage needed
**Description**
I want to use a peer macro to create a new type that conforms… to the same protocols as the type it's attached to. This seems to work for `Hashable`, `Encodable`, `Decodable`, but when I try to implement `Equatable`, I get compiler errors. Here's a reduction of the problem:
```swift
struct Inequatable {}
@Duplicate
struct S: Equatable {
let preventSynthesis = Inequatable()
static func == (lhs: Self, rhs: Self) -> Bool {
true
}
}
```
And this is what the `Duplicate` macro expands to:
```swift
struct S_Duplicate: Equatable {
let preventSynthesis = Inequatable()
static func == (lhs: Self, rhs: Self) -> Bool {
true
}
}
```
I get: `error: type 'S_Duplicate' does not conform to protocol 'Equatable'`
For completeness, here's my stupid macro:
```swift
public struct DuplicateMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
var dup = declaration.as(StructDeclSyntax.self)!
dup.attributes = [] // remove the macro to prevent recursion
// dup.inheritanceClause = nil // remove the Equatable conformance (everything will compile, but S_Duplicate won't actually be Equatable)
dup.name = TokenSyntax(.identifier("\(dup.name.text)_Duplicate"), presence: .present)
return [dup.as(DeclSyntax.self)!]
}
}
@main
struct MacroEquatablePlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
DuplicateMacro.self,
]
}
```
and its declaration:
```swift
@attached(peer, names: suffixed(_Duplicate))
public macro Duplicate() = #externalMacro(module: "MacroEquatableMacros", type: "DuplicateMacro")
```
**Steps to reproduce**
Use Xcode's "new package" to create a macro package. Delete the unit tests.
Replace the macro declaration, macro implementation, and client code with the above snippets.
Compile (or rather, fail to). Full error output:
```
@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:1:8: error: type 'S_Duplicate' does not conform to protocol 'Equatable'
struct S_Duplicate: Equatable {
^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:2:9: note: stored property type 'Inequatable' does not conform to protocol 'Equatable', preventing synthesized conformance of 'S_Duplicate' to 'Equatable'
let preventSynthesis = Inequatable()
^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
Swift.==:1:24: note: candidate would match if 'S_Duplicate' conformed to 'RawRepresentable'
@inlinable public func == <T>(lhs: T, rhs: T) -> Bool where T : RawRepresentable, T.RawValue : Equatable
^
Swift.FloatingPoint:2:24: note: candidate would match if 'S_Duplicate' conformed to 'FloatingPoint'
public static func == (lhs: Self, rhs: Self) -> Bool
^
Swift.BinaryInteger:2:24: note: candidate would match if 'S_Duplicate' conformed to 'BinaryInteger'
public static func == <Other>(lhs: Self, rhs: Other) -> Bool where Other : BinaryInteger
^
Swift._Pointer:2:24: note: candidate would match if 'S_Duplicate' conformed to '_Pointer'
public static func == (lhs: Self, rhs: Self) -> Bool
^
Swift._Pointer:3:35: note: candidate would match if 'S_Duplicate' conformed to '_Pointer'
@inlinable public static func == <Other>(lhs: Self, rhs: Other) -> Bool where Other : _Pointer
^
Swift.Strideable:3:35: note: candidate would match if 'S_Duplicate' conformed to 'Strideable'
@inlinable public static func == (x: Self, y: Self) -> Bool
^
Swift.StringProtocol:2:35: note: candidate would match if 'S_Duplicate' conformed to 'StringProtocol'
@inlinable public static func == <RHS>(lhs: Self, rhs: RHS) -> Bool where RHS : StringProtocol
^
Swift.SIMD:4:24: note: candidate would match if 'S_Duplicate' conformed to 'SIMD'
public static func == (a: Self, b: Self) -> Bool
^
@__swiftmacro_20MacroEquatableClient1S9DuplicatefMp_.swift:1:8: note: do you want to add protocol stubs?
struct S_Duplicate: Equatable {
^
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
/Users/<me>/Source/SwiftReductions/MacroEquatable/Sources/MacroEquatableClient/main.swift:6:1: note: in expansion of macro 'Duplicate' here
struct S: Equatable {
^~~~~~~~~~~~~~~~~~~~~
```
**Expected behavior**
I expect my macro-generated struct to be able to conform to `Equatable`
**Environment**
`$ swiftc --version`
> swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
> Target: arm64-apple-macosx13.0
`$ xcodebuild -version`
> Xcode 15.0
> Build version 15A240d
opened 11:34AM - 06 Oct 23 UTC
bug
triage needed
<!--
This repository tracks issues related to the implementation of the Swift
… compiler, standard library, runtime, and tools that provide IDE support for
Swift (e.g. code completion). If the bug relates to the implementation of a
proprietary (closed-source) Apple framework such as UIKit, SwiftUI, Combine,
etc., please report it to https://feedbackassistant.apple.com instead.
-->
**Description**
When accessing a static member of an extension (added via `ExtensionMacro`) outside the file that it is applied in, we get the following error `Error: Type 'any Foo' has no member 'default`. Access should be allowed outside the file it is applied (within the same module, if internal)
**Steps to reproduce**
<!--
Explain how to reproduce the problem (in steps if seen fit) and include either
an inline test case (preferred) or a project that reproduces it. Consider
reducing the sample to the smallest amount of code possible — a smaller test
case is easier to reason about and more appealing to contributors.
-->
Sample project: [DefaultProviding.zip](https://github.com/apple/swift/files/12830467/DefaultProviding.zip)
- Verify access is allowed in `Foo.swift`
```
// Testing accessing `.default()` in `Foo.swift`
let t: Foo = .default() // OK
```
- Verify that access is not allowed in `main.swift`
```
// Testing accessing `.default()` in `main.swift`
let m: Foo = .default() // Error: Type 'any Foo' has no member 'default
```
**Expected behavior**
Code should compile without errors.
<!--
Include information about the Swift compiler version and, if applicable, the
Xcode version you are observing the problem in and the deployment target.
-->
**Environment**
- Swift compiler version info
swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx14.0
- Xcode version info
Xcode 15.0
- Deployment target:
iOS 17/macOS Sonoma
opened 05:22AM - 29 Nov 23 UTC
bug
triage needed
### Description
I have an extension macro that is intended to be applied to p… rotocol declarations. The macro provides default implementations of all of its members via an extension. If I declare a struct or class `Foo` which implements the protocol via those default implementations, I am only able to access the protocol members on an instance of `Foo` from the same file in which `Foo` was declared. Anywhere else in the same module, I get a "Value of type 'Foo' has no member 'member'" error. `(fooInstance as Proto).member` does work in either file, however.
### Reproduction
Full code also available [here](https://github.com/zachwolfe/ProtocolMembers).
Steps to reproduce:
1. Create a new macro called `ProtocolMembers`
2. Replace source of `ProtocolMembers/ProtocolMembers.swift` with the following:
```swift
@attached(extension, names: arbitrary)
public macro ImplementProtocolMembers() = #externalMacro(module: "ProtocolMembersMacros", type: "ImplementProtocolMembersMacro")
```
3. Replace source of `ProtocolMembersMacros/ProtocolMembersMacro.swift` with the following:
```swift
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxMacros
public struct ImplementProtocolMembersMacro: ExtensionMacro {
public static func expansion(of node: SwiftSyntax.AttributeSyntax, attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, conformingTo protocols: [SwiftSyntax.TypeSyntax], in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] {
[
try ExtensionDeclSyntax("extension NonWorkingProtocol") {
"var property: Int { 42 }"
}
]
}
}
@main
struct ProtocolMembersPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
ImplementProtocolMembersMacro.self
]
}
```
4. Replace source of `ProtocolMembersClient/main.swift` with the following. Note that `NonWorking{Protocol, Struct}` only differ from their `Working*` counterparts in that `property` is provided by a macro instead of having been added manually:
```swift
import ProtocolMembers
// =============== Working stuff ===============
protocol WorkingProtocol {
var property: Int { get }
}
extension WorkingProtocol {
var property: Int { 42 }
}
struct WorkingStruct: WorkingProtocol {
}
// ============= Non-working stuff =============
@ImplementProtocolMembers
protocol NonWorkingProtocol {
var property: Int { get }
}
struct NonWorkingStruct: NonWorkingProtocol {
}
// ================== Testing ==================
func takeValuesInSameFile(working: WorkingStruct, nonWorking: NonWorkingStruct) {
// In this file, all three of these lines compile (as expected):
_ = working.property
_ = (nonWorking as any NonWorkingProtocol).property
_ = nonWorking.property
}
```
5. Finally, add another Swift file in `ProtocolMembersClient` with the following source:
```swift
// ================== Testing ==================
func takeValuesInSecondaryFile(working: WorkingStruct, nonWorking: NonWorkingStruct) {
// In this file, these two lines compile:
_ = working.property
_ = (nonWorking as any NonWorkingProtocol).property
// But this one does not (unexpected):
// _ = nonWorking.property
}
```
### Expected behavior
I expected that `property` would be accessible on instances of `NonWorkingStruct` regardless of which file in the module the access occurs in.
### Environment
Tested on Swift 5.9 included with Xcode, as well as the current development snapshot as of writing:
```
% swift -version
swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
```
### Additional information
_No response_
opened 02:31PM - 02 Mar 24 UTC
feature
triage needed
_Not completely sure this is a bug report or a feature request. Don't think ther… e is a spec for this case._
### Motivation
Imagine attaching a dummy `@attached(member) macro Dummy() = #externalMacro(...)` macro to a type. A macro does literally nothing, aka here's it's implementation:
```swift
public struct DummyMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
[]
}
}
```
Attaching a macro messes up the ability to declare conformance to `Equatable` to the types where `Equatable` conformance cannot be synthesised automatically (like for e.g. tuples, which still cannot conform to protocols):
```swift
// This behaves as expected
struct Foo: Equatable {
var bar: (Int, Int)
static func == (lhs: Self, rhs: Self) -> Bool { lhs.bar == rhs.bar }
}
@Dummy()
struct FooToo: Equatable { // Type 'FooToo' does not conform to protocol 'Equatable'
var bar: (Int, Int)
static func == (lhs: Self, rhs: Self) -> Bool { lhs.bar == rhs.bar }
}
```
Of course one can declare `Equatable` conformance as an extension on `FooToo`, but this is not an ergonomic solution for my case.
I am implementing a [StaticArray](https://github.com/malien/StaticArray) macro, to generate C-like fixed sized array, while there are still [uncertainties in fixed-size arrays being introduced to the swift language](https://forums.swift.org/t/approaches-for-fixed-size-arrays).
Since [tuples don't conform to `Equatable`](https://www.google.com/search?hl=en&q=swift%20equatable%20tuple), I'd like to synthesize an implementation, only for the cases where user declared that they desire the conformance (and presumably have proved that the inner type is `Equatable` as well):
```swift
@StaticArray<UInt8>(count: 4)
struct IPv4: Equatable { }
// ^ Synthesize `static func ==(lhs:, rhs:)` as a part of macro expansion
```
As stated above, compiler ignores any manual conformance to the `Equatable` inside of the struct's body.
### Proposed solution
Allow manual `Equatable`/`Comparable`/`Hashable` conformance inside of the type's definition, when attaching macros.
...[or make tuples conform to `Equatable`/`Comparable`/`Hashable`](https://github.com/apple/swift-evolution/blob/main/proposals/0283-tuples-are-equatable-comparable-hashable.md)
...[or make tuples to conform to any protocol](https://forums.swift.org/t/pitch-user-defined-tuple-conformances/67154)
...[or implement fixed-size arrays into the language](https://forums.swift.org/t/approaches-for-fixed-size-arrays/58894), rendering my efforts of backporting the feature redundant
### Alternatives considered
_No response_
### Additional information
_No response_
Can anyone help get this fixed? (I got absolutely nowhere trying to find the problem in the compiler source myself!)
2 Likes
@Douglas_Gregor perhaps? (sorry)
robdo
(Robin)
April 8, 2024, 7:21pm
3
In case anyone else ends up here when faced with this bug, it seems to be fixed on the latest toolchain snapshot (April 4, 2024), if using that is an option for you
hborla
(Holly Borla)
April 9, 2024, 12:27am
6
Yes, all of the bugs that I know of around visibility of methods added by extension macros, which could manifest as protocol conformance failures, were fixed by [Macros] Reproduce issue with peer+extension macro extension's methods not being checked as witnesses by ktoso · Pull Request #71717 · apple/swift · GitHub .
1 Like