swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.57k stars 10.36k forks source link

Protocol requirement implemented by macro not visible in another file #70085

Open zachwolfe opened 11 months ago

zachwolfe commented 11 months ago

Description

I have an extension macro that is intended to be applied to protocol 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.

Steps to reproduce:

  1. Create a new macro called ProtocolMembers
  2. Replace source of ProtocolMembers/ProtocolMembers.swift with the following:
    @attached(extension, names: arbitrary)
    public macro ImplementProtocolMembers() = #externalMacro(module: "ProtocolMembersMacros", type: "ImplementProtocolMembersMacro")
  3. Replace source of ProtocolMembersMacros/ProtocolMembersMacro.swift with the following:
    
    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
}
  1. Finally, add another Swift file in ProtocolMembersClient with the following source:
    
    // ================== 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_
red-beeard commented 11 months ago

This may be related to, or a duplicate of, #68515 #68683 #69013