quad/.claude/luau-test/08-type-source-satisfies-state.luau
qwreey df4a77b02d
docs(luau-test): M0 사전검증 스파이크 신설, luau-ignoreme→.claude/luau-test 이동
base/ 확정 사항 중 아직 실제 Luau로 부딪혀본 적 없는 것(M0 스파이크
대상)을 사용자가 luau/luau-analyze/luau-lsp/Roblox Studio로 직접
돌려볼 수 있는 독립 실행 스크립트 14개 + README 색인으로 정리. 커밋
f198fd9의 정정사항(Ref 콜백/대기자 배열 소진을 None에서 nil로 되돌린
것 등)을 반영해 02번을 재작성했고, 타입 관련 실측이 필요한 항목
(Attribute 제네릭 DI 키 narrowing, Ref/PreRef 구조적 서브타입, Source/
Ref nilable-default 오버로드)을 새로 찾아 12~14번으로 추가함. 처음엔
git 자동 제외 폴더(luau-ignoreme/)에 만들었으나 커밋해서 레포에
남기기로 해 .claude/luau-test/로 이동, .claude/README.md에 색인 추가.
CLAUDE.md에 세션 요약 반영 — 아직 실행 결과는 미확인.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 23:55:22 +09:00

78 lines
3.5 KiB
Text

--!strict
--[[
검증 대상: Source<T>가 구조적으로 State<T>를 만족하는(self 타이핑 +
State 참조가 섞인 제네릭 :Compute) 설계가 Luau 타입 솔버에서 안전하게
추론되는지 — 실제 실행이 아니라 타입 체크(luau-analyze) 대상.
배경: .claude/base/store-semantics.md "검증 필요(확정 아님, M0 스파이크
대상)" 절, ROADMAP.md M0 2번째 항목.
핵심 우려: State<T>가 거꾸로 Source를 참조하는 "상호 재귀"는 Luau
솔버가 취약한 패턴 — 그래서 아래 State<T>는 Source를 전혀 참조하지
않도록 독립적으로 먼저 정의하고, Source<T>만 State<T>를 단방향으로
참조하게 구성함. 타입은 사용자 선호대로 &(교차)가 아니라 손으로
펼쳐 씀(런타임 구현 델리게이션과는 별개 축이라 상관없음).
실행: `luau-analyze 08-type-source-satisfies-state.luau`
(로컬에 luau-analyze가 없으면 Luau 공식 릴리즈의 CLI 툴체인 설치 필요 —
https://github.com/luau-lang/luau/releases, 또는 lune 배포판에 포함된 것)
기대 결과: 에러 없이 통과하거나, 통과 안 하면 정확히 *어느 줄에서*
*무슨 에러*가 나는지가 다음 결정에 중요한 정보임 — 에러가 나면 그
메시지를 그대로 가져와서 알려줄 것.
]]
-- State<T>는 Source를 절대 참조하지 않음(단방향 의존을 위한 핵심 제약)
export type State<T> = {
Get: (self: State<T>) -> T,
With: (self: State<T>, ...State<any>) -> State<any>,
Compute: <U>(self: State<T>, fn: (T) -> U) -> State<U>,
}
-- Source<T>만 State<T>를 참조(단방향) — self 타이핑(Source<T> 자신을 가리킴)과
-- 바깥 타입 참조(State<U>)가 섞인 제네릭 메소드가 바로 검증 대상
export type Source<T> = {
Get: (self: Source<T>) -> T,
With: (self: Source<T>, ...State<any>) -> State<any>,
Compute: <U>(self: Source<T>, fn: (T) -> U) -> State<U>,
Set: (self: Source<T>, value: T) -> (),
Emit: (self: Source<T>) -> (),
}
-- 1. Source 값을 만드는 흉내 생성자(런타임 구현은 아직 없으므로 타입만 맞추는 더미)
local function fakeSource<T>(default: T): Source<T>
return (nil :: any) :: Source<T>
end
-- 2. State<T>를 요구하는 함수에 Source<T>를 그대로 넘길 수 있는가
-- (구조적 서브타이핑 — "Source가 State를 만족함" 절의 핵심 주장)
local function useAsState<T>(s: State<T>): T
return s:Get()
end
local mySource: Source<number> = fakeSource(0)
local viaSubtype: number = useAsState(mySource) -- 여기가 타입체크 되는지가 핵심
-- 3. Compute 체이닝이 제네릭을 타고 잘 흐르는가(Source -> State<string> -> State<boolean>)
local derived1: State<string> = mySource:Compute(function(n: number): string
return tostring(n)
end)
local derived2: State<boolean> = derived1:Compute(function(s: string): boolean
return #s > 0
end)
-- 4. store.key가 Source<T>를 직접 반환한다는 모델(레코드 필드 읽기/쓰기 대칭)
export type Store = {
-- 실제로는 defaults의 각 키를 Source<...>로 매핑하는 mapped type이 이상적이지만
-- Luau에 mapped type이 없으므로(2026-08 시점) 구체 예시 하나로만 검증
Health: Source<number>,
}
local function useStore(store: Store)
store.Health:Set(100) -- 쓰기
local hp: number = store.Health:Get() -- 읽기 — 같은 필드 타입(Source<number>)으로 대칭
return hp
end
print("이 파일은 luau-analyze로만 의미가 있음 (런타임 실행은 그냥 통과함)")
print(viaSubtype, derived2)