병렬 에이전트 5개(디스패치 코어/프리미티브/research·reference·archive/ 인덱스 레이어/luau-test)로 재감사, 실제 문제만 수정: - bind-system-plan.md/store-semantics.md: :Compute/:With lazy 핸들 계약이 Luau 추론과 충돌한다는 사실(question.md 0-Y)이 정작 그 계약을 서술하는 두 파일엔 경고 배너 없이 "확정"으로만 남아있던 것 수정 - slot-plan.md: process(inst,k,self) 3-인자 표기 5곳(현재는 4-인자 process(inst,k,v,index)) 정정, rawUnmount의 index-기준 시그니처와 reconcile 호출부의 element-기준 인자 불일치 캐비엇 추가, 폐기된 "Handler.retract" 표현 정정 - README.md: question.md 0-A 오타(실제는 0-Z) 수정 - ROADMAP.md: M7 NoneHandler 항목에 M2/M4/M10과 같은 재디스패치 모델 교체 경고 배너 누락돼 있던 것 추가 - documentation-content-map.md: 이미 확정된 Tween 옵션 질문이 "아직 열림"으로 남아있던 것 정정, 예시 코드가 2026-08-10 폐기된 구 Tween 특수 bind key 모델을 쓰고 있던 것에 캐비엇 추가 - luau-test/13 헤더: "런타임은 그냥 통과함" 예상이 실측(STATUS.md)과 반대였던 것 정정 - luau-test/STATUS.md: 15번 파일이 🔴/🟠 두 테이블에 중복 등재돼 건수가 안 맞던 것 정리 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
140 lines
5.7 KiB
Text
140 lines
5.7 KiB
Text
--!strict
|
|
--[[
|
|
검증 대상: 2026-08-09 열한 번째 세션(커밋 f198fd9)에서 뒤집힌 결정 —
|
|
`isRef`/`isPreRef`가 "서로 배타적인 형제 브랜드"에서 "Source가 State를
|
|
만족하는 것과 같은 포함 관계(PreRef가 Ref의 하위 개념)"로 재정정됨.
|
|
이전엔 `isRef(preRefInstance) == false`였는데, 지금은
|
|
`isRef(preRefInstance) == true`로 바뀜.
|
|
|
|
이 파일은 두 부분으로 나뉨:
|
|
A) 타입 체크 대상 — `PreRef<T>`가 구조적으로 `Ref<T>`를 만족하는지
|
|
(08번 파일이 Source/State에 대해 검증한 것과 정확히 같은 질문을
|
|
Ref/PreRef에 대해 재검증).
|
|
B) 런타임 대상 — `isRef`/`isPreRef` predicate 합성이 문서에 적힌 대로
|
|
동작하는지, 그리고 `Dispatch/Leaf.luau`의 `(v=Ref)` 매치 핸들러가
|
|
이제 `isHandlable = isRef(v) and not isPreRef(v)`로 **명시적으로
|
|
좁혀야만** PreRef를 잘못 삼키지 않는다는 것.
|
|
|
|
배경: .claude/base/bind-system-plan.md의 `Brand` 절
|
|
("isRef(x)는 그 위에 Brand.get(x)==RefTag를 OR로 얹은 상위 개념")와
|
|
"`(v=Ref)` children 배열 leaf 매치 핸들러... isRef(v) and not
|
|
isPreRef(v)로 명시적으로 좁혀야 함" 부분.
|
|
|
|
실행:
|
|
A) `luau-analyze 13-type-ref-preref-subtype.luau` (또는 luau-lsp)
|
|
B) `luau 13-type-ref-preref-subtype.luau`
|
|
|
|
[정정, 2026-08-13 첫 실측 라운드] 위 "런타임 부분은 그냥 통과함" 예상은
|
|
틀렸음 — 실제로 돌려보니 A섹션의 `fakePreRef(0)`가 런타임에 `nil`을
|
|
반환하는 더미 스텁이라, B섹션이 시작하기 전에 `useAsRef(myPreRef)`의
|
|
`r.Value` 접근에서 "attempt to index nil"로 죽어 B섹션 런타임 검증까지
|
|
도달하지 못함. 타입 A섹션(luau-analyze)은 별개로 통과. 상세는
|
|
`luau-test/STATUS.md` 🟠 항목 — A/B를 별도 파일로 분리해야 함(재작성
|
|
대기).
|
|
]]
|
|
|
|
-- ===== A) 타입 체크 대상 =====
|
|
|
|
export type Ref<T> = {
|
|
Value: T,
|
|
Set: (self: Ref<T>, value: T) -> Ref<T>,
|
|
Callback: (self: Ref<T>, fn: (T) -> ()) -> Ref<T>,
|
|
Wait: (self: Ref<T>, thread: thread?) -> Ref<T>,
|
|
}
|
|
|
|
-- PreRef는 "Ref 런타임을 재사용하되 브랜드 태그만 다름"이라고 문서가
|
|
-- 명시함 — 타입도 필드 구성이 완전히 동일해야 자연스러움(브랜드 차이는
|
|
-- 런타임 전용이라 정적 타입엔 안 드러남, 아래서 별도 nominal 표시로만 구분)
|
|
export type PreRef<T> = {
|
|
Value: T,
|
|
Set: (self: PreRef<T>, value: T) -> PreRef<T>,
|
|
Callback: (self: PreRef<T>, fn: (T) -> ()) -> PreRef<T>,
|
|
Wait: (self: PreRef<T>, thread: thread?) -> PreRef<T>,
|
|
}
|
|
|
|
local function fakePreRef<T>(default: T): PreRef<T>
|
|
return (nil :: any) :: PreRef<T>
|
|
end
|
|
|
|
-- 시도: PreRef<T> 값을 Ref<T>가 필요한 자리에 그대로 넘길 수 있는가
|
|
local function useAsRef<T>(r: Ref<T>): T
|
|
return r.Value
|
|
end
|
|
|
|
local myPreRef: PreRef<number> = fakePreRef(0)
|
|
local viaSubtype: number = useAsRef(myPreRef) -- <- 여기가 luau-analyze 확인 포인트
|
|
|
|
print("A) 타입 체크는 luau-analyze/luau-lsp로 확인 — 런타임은 그냥 통과")
|
|
print(viaSubtype)
|
|
|
|
-- ===== B) 런타임 대상 — Brand/isRef/isPreRef predicate 합성 =====
|
|
|
|
local Brand = {}
|
|
local registry = setmetatable({}, { __mode = "k" })
|
|
function Brand.set(x, tag)
|
|
registry[x] = tag
|
|
end
|
|
function Brand.get(x)
|
|
return registry[x]
|
|
end
|
|
|
|
local RefTag, PreRefTag = {}, {}
|
|
|
|
local function isPreRef(x)
|
|
return Brand.get(x) == PreRefTag
|
|
end
|
|
local function isRef(x)
|
|
-- 재정정된 합성 — PreRef가 Ref의 하위 개념(OR로 얹음)
|
|
return isPreRef(x) or Brand.get(x) == RefTag
|
|
end
|
|
|
|
local function makeRef()
|
|
local self = {}
|
|
Brand.set(self, RefTag)
|
|
return self
|
|
end
|
|
local function makePreRef()
|
|
local self = {}
|
|
Brand.set(self, PreRefTag)
|
|
return self
|
|
end
|
|
|
|
local ref1 = makeRef()
|
|
local preref1 = makePreRef()
|
|
|
|
print()
|
|
print("=== B-1. isRef/isPreRef 기본 동작 ===")
|
|
print("isRef(ref1) =", isRef(ref1), "(true여야 함)")
|
|
print("isPreRef(ref1) =", isPreRef(ref1), "(false여야 함 — Ref는 PreRef가 아님)")
|
|
print("isRef(preref1) =", isRef(preref1), "(true여야 함 — 2026-08-09 재정정의 핵심)")
|
|
print("isPreRef(preref1) =", isPreRef(preref1), "(true여야 함)")
|
|
|
|
-- Dispatch/Leaf.luau의 (v=Ref) 매치 핸들러 흉내 — PreRef를 잘못 삼키면 안 됨
|
|
local function leafRefHandlerIsHandlable(v)
|
|
return isRef(v) and not isPreRef(v)
|
|
end
|
|
|
|
print()
|
|
print("=== B-2. Leaf의 (v=Ref) 핸들러가 PreRef를 잘못 삼키지 않는가 ===")
|
|
print("leafRefHandlerIsHandlable(ref1) =", leafRefHandlerIsHandlable(ref1), "(true — 일반 Ref는 처리해야 함)")
|
|
print(
|
|
"leafRefHandlerIsHandlable(preref1) =",
|
|
leafRefHandlerIsHandlable(preref1),
|
|
"(false여야 함 — PreRef는 pre-pass가 이미 처리했어야 하고, 이 핸들러가 또 삼키면 안 됨)"
|
|
)
|
|
|
|
assert(leafRefHandlerIsHandlable(ref1) == true, "일반 Ref가 Leaf 핸들러에서 거부됨 (버그)")
|
|
assert(leafRefHandlerIsHandlable(preref1) == false, "PreRef가 Leaf 핸들러에 잘못 잡힘 (버그 — 2026-08-09 재정정이 요구하는 명시적 좁히기 실패)")
|
|
print()
|
|
print("assert 전부 통과 — isRef(v) and not isPreRef(v) 조합이 기대대로 동작함")
|
|
|
|
--[[
|
|
확인 포인트:
|
|
A) luau-analyze/luau-lsp에서 `viaSubtype` 줄이 에러 없이 통과하는가 —
|
|
08번 파일이 Source/State에 대해 확인했던 것과 같은 결론(구조적
|
|
서브타이핑 성립)이 Ref/PreRef에도 그대로 적용되는지.
|
|
B) 런타임 assert가 전부 통과하는가 — 특히 `isRef(preref1) == true`
|
|
(뒤집힌 결정 자체)와 `leafRefHandlerIsHandlable(preref1) == false`
|
|
(그 뒤집힘 때문에 Leaf 핸들러가 이제 반드시 `not isPreRef(v)`를
|
|
같이 확인해야 한다는 요구사항)가 실제로 필요한 조합인지.
|
|
]]
|