How to access a protocol property from another protocol without exposing the other protocol in the API?

my framework:

struct Unimplemented: Error {
    let year: Int // need to throw with a year value
    let day: Int
    let part: Int
}

protocol AdventDay: Sendable { // maybe can be a class? but how would i enforce a particular interface?
    associatedtype Answer = Int
    nonisolated(unsafe) static var day: Int { get }
    
    init(input: String)
    func part1() async throws -> Answer
    func part2() async throws -> Answer
}

extension AdventDay {
    init() { // maybe i could take year here, but how would i store it without requiring the conformer to specify it? (i.e without leaking this implementation detail to the user)
        self.init(input: "")
    }
    
    func part1() throws -> Answer {
        throw Unimplemented(day: Self.day, part: 1) // currently error, needs to access a year value
    }
    
    func part2() throws -> Answer {
        throw Unimplemented(day: Self.day, part: 2) // currently error, needs to access a year value
    }
}

protocol AdventYear: Sendable {
    var year: Int { get } // the year value in question
    var days: [any AdventDay.Type] { get }
}

@main
struct Main {
    static func main() async throws {
        var years: [AdventYear] = // a list of all types that conforms to AdventYear
        for year in years {
            for day in year.days {
                var day = day.init() // maybe i could pass year value here?
                // ...
            }
        }
    }
}

the user code i expect / want to achieve:

struct HistorianHysteria: AdventDay { // should not be exposed to year
    static var day = 1
    var input: String
}

struct RedNosedReports: AdventDay { // should not be exposed to year
    static var day = 2
    var input: String
}

struct Challenges: AdventYear {
    var year = 2024 // the year value in question is defined here
    var days: [any AdventDay.Type] = [
        HistorianHysteria.self,
        RedNosedReports.self,
    ]
}

ps: i wrote a walkthrough of the problem and my questions in the comments inside the code blocks.

if i could do this then my problem would be solved, but swift doesn't allow this.

extension AdventDay {
    private var year: Int
    
    init(year: Int) {
        self.year = year
        self.init(input: "")
    }
    
    func part1() throws -> Answer {
        throw PartUnimplemented(year: year, day: Self.day, part: 1)
    }
    
    func part2() throws -> Answer {
        throw PartUnimplemented(year: year, day: Self.day, part: 2)
    }
}

which i then can do

@main
struct Main {
    static func main() async throws {
        var years: [AdventYear] = // a list of all types that conforms to AdventYear
        for year in years {
            for day in year.days {
                var day = day.init(year: year.year)
                // ...
            }
        }
    }
}

Is there anything else in these structs? So far they look very similar. I'm not sure you really need different types here. Maybe single struct AdventDay would be enough?

You have day as static and input as non-static. Can there be multiple inputs per day? Where are inputs coming from?

Is there anything else in these structs? So far they look very similar. I'm not sure you really need different types here. Maybe single struct AdventDay would be enough?

So this is basically an Advent of Code framework, each advent day has one challenge in two parts, each part operate on the same input, however the input might require to be manipulated before first and second part work on them, which is the difference between them. here is an example from the official swift advent of code template: swift-aoc-starter-example/Sources/Day00.swift at main · swiftlang/swift-aoc-starter-example · GitHub

You have day as static and input as non-static. Can there be multiple inputs per day? Where are inputs coming from?

There is only one input per day afaik, the inputs will be fetched from a third party web api. I actually didn't intend the day property to be static, when it's not static swift complains about the struct not conforming to AdventDay.

Can concrete implementations be shared between several years? IIUC, not. Then each struct knows which day of which year it implements. I would put year next to a day:

protocol AdventDay: Sendable {
    associatedtype Answer = Int
    nonisolated(unsafe) static var day: Int { get }
    nonisolated(unsafe) static var year: Int { get }
    
    init(input: String)
    func part1() async throws -> Answer
    func part2() async throws -> Answer
}

Can concrete implementations be shared between several years? IIUC, not.

In Advent of Code, no, but for my framework it doesn't matter - what i hope to provide is a more dynamic "template", i.e a structure like this:

AdventOfCode/
  Sources/
    2024/...
    2025/...
  Package.swift

so every 25 struct in each year defining a year property doesn't really feel optimal for me, both because it violates DRY and also because it makes my API confusing:

struct HistorianHysteria: AdventDay {
    static var year = 2024
    static var day = 1
    var input: String
}

struct RedNosedReports: AdventDay {
    static var year = 2024
    static var day = 2
    var input: String
}

// 23 more structs conforming to AdventDay

struct Challenges: AdventYear {
    // when you could just define the year here, as implied by AdventYear
    var days: [any AdventDay.Type] = [
        HistorianHysteria.self,
        RedNosedReports.self,
    ]
}

Where are part1() and part2() called from? Does their caller know about year? Maybe even about day too? What if single-day solutions throw an error without year (and day?) and the caller catches it, and maps to a different error type, which now contains year (and date).

Instead of AdventDay.day can we use indices of AdventYear.days?

I've refactored the code a bit but yes that's what i eventually landed at, to not associate day and year with the actual values at all and instead map the error to something else at the call site where year and day will be accessible, thank you!

But I did get stuck on the "where year and day will be accessible" :cry: I decided that the folder name representing the year and the filename representing the day was the most ergonomic/easiest way for the user, so something like follows:

Sources/
  2024/
    01.swift # day one
    02.swift # day two
    ...

now i need to access the file name and the folder name, which seems i'm able to do via #file and #filePath but the question remains how do i make that accessible to the main call site? From the little googling i've done it looks like I need to create a macro, so I'm imagining the user code to look like:

struct HistorianHysteria: Day {
    var input: String
    
    func partOne() -> Int {
        return 11
    }
    
    func partTwo() -> Int {
        return 12
    }
}

#register(HistorianHysteria)

where if register were to be a regular function it'd look something like

func register(_ type: Day.Type) {
    challenges[#filePath][#file] = type
}

where challenges is defined in the main file as

nonisolated(unsafe) var challenges: [String: [String: any Day.Type]] = [:]

but how do i actually implement this macro (do i really even need to, is there a simpler way to achieve this)? this is my first time getting my hands dirty with macros in general, and the swift docs didn't really help me here it was pretty confusing