How to get access level of a type a macro is attached to?

Let's look at the following case where I want to use macros to extend an enum type as follows:

Source:

enum MyEnum {
    case intValue(Int)
    case stringValue(string: String)
}

Destination:

enum MyEnum {
    case intValue(Int)
    case stringValue(string: String)

    enum CaseLabel {
        case intValue, stringValue
    }

    var caseLabel: CaseLabel {
        switch self {
        case .intValue:
            .intValue
        case .stringValue:
            .stringValue
        }
    }
}

extension MyEnum: CaseLabeled {}

While CaseLabeled is a public protocol

public protocol CaseLabeled {
    associatedtype CaseLabel: Equatable
    var caseLabel: CaseLabel { get }
}

I have implemented this with a member and an extension macro.

If MyEnum is public, the CaseLabel and caseLabel declarations must also be public due to the public protocol conformance:

public enum MyEnum {
    case intValue(Int)
    case stringValue(string: String)

    public enum CaseLabel {
        case intValue, stringValue
    }

    public var caseLabel: CaseLabel {
        switch self {
        case .intValue:
            .intValue
        case .stringValue:
            .stringValue
        }
    }
}

What I can do in the MemberMacro implementation is to retrieve the modifiers of the enum to find out if CaseLabel and caseLabel need to be made public.

So sth. like

// 'declaration' is an EnumDeclSyntax
let isPublicEnum = declaration.modifiers.contains { modifier in
    modifier.name.text == "public"
}

But this only works if the developer doesn't put the enum declaration into a public extension like this:

public extension SomeType {
    @CaseLabeled
    enum MyEnum {
        case intValue(Int)
        case stringValue(string: String)
    }
}

This results in MyEnum also being public. But in this case I do not have a corresponding modifier in the macro code at the node, as it is present on the enclosing element.
As a result, CaseLabel and caseLabel on the expanded enum do not have the correct access level.

So the question is:
How do I get the effective access level of the type to be extended by the macro?

It doesn't seem like you can get information about the parent node hierarchy in a macro.

Of course, you can ask the developers to make sure that they always define the access level at the enum itself when using the macro, but that doesn't seem like a good solution to me.

Macros operate on raw syntax and not on AST nodes annotated with inferred types or access levels.

This semantic information is available after macro's output is produced. What you're asking for is not available at the early compilation stage when macros are still evaluated.

Let me perhaps rephrase my question:
How would you solve the problem described?

Via a macro parameter that the developer can set and that sets the appropriate access level in the expanded code?

I think you can always output public, those won't be visible anyway if the type visibility is less than public

2 Likes

This actually seems to be a simple and effective option. I would have expected the compiler to at least issue a warning if you make a public declaration on a private type. But that does not seem to be the case.

I think that's a solution I can live with for the time being. Thank you very much for that!

In the meantime, I have also found out that work is being done to make the parent context available to macros:

However, I have not yet taken a closer look at the new lexicalContext.

It's a deliberate design decision that access levels of members inside of a type are clamped to the types access level. This is to reduce churn when increasing the visibility of a type.