lloyAlloy on GitHub
each
Docs

each

Updated
enum Lifecycle
    Init
    Start
end

attribute provider(lifecycles: Lifecycle[]) on impl as
    requires private function each lifecycles(self)
end

@provider({ lifecycles = [ Lifecycle.Init, Lifecycle.Start ] })
impl PlayerData
    private function Init(self)
    end

    private function Start(self)
    end
end

Writes one requires clause per entry of a list argument, with the entry as the member name. One attribute then asks for what the user opted into: [ Init, Start ] asks for two methods, [ Start ] asks for one, and an empty list asks for none.

An attribute argument is already a literal the compiler reads, which is what makes Attributes.get and @ratelimit(5) work, so each reads a constant. It runs no code and needs no type function.

An enum variant names the member by its own name and a string names it by its text, so [ "Init" ] and [ Lifecycle.Init ] ask for the same member. The enum form reads better and completes, and it is the one to write.

The argument comes either way: @provider([ Init ]) by position, or @provider({ lifecycles = [ Init ] }) by key. The parameter must be a list, and the declaration reports when it is not.

The word is free as a name. each reads as a keyword only between a clause's kind word and its parameter, so local each = 1 and t.each(f) keep their Luau meaning.