quad/.claude/session/2026-08-15-02-try-callback-investigation-and-review-fixes.md
qwreey 6bfd93fd62
docs: 콜백 파라미터 무주석 추론 전방위 재시도, code-review 2회전 정합성 수정
type-recursive-issue-try-callback/ 신설(spikes 35개) — type function/
메타테이블/제네릭/이중 꺾쇠 명시 인스턴스화까지 전방위로 재시도했으나
quad의 state:Compute(fn) 단일 호출 모양을 유지한 채로는 여전히 안 됨,
근본 원인은 재귀 특유가 아니라 "제네릭 콜백 인자엔 컨텍스트 타입
전파가 안 됨"이라는 더 일반적 Luau 한계임을 확인. typing-limits.md
§1/§7에 반영, 원칙 자체는 안 바뀜.

이어진 /code-review 2회전이 이 작업과 luau-test/16 복구 세션
전반에서 정합성 문제 다수 발견 — 스파이크 결과 표 stale(16번 실패로
잔존), 폴더 개수 off-by-one 여러 건, type-recursion-issue/REPORT.md의
실제 오류(self가 제네릭이면 자유 함수로 빼도 여전히 실패한다는 사실이
"통과"로 잘못 서술됨), 옛 솔버 캐비엇 누락, 체크리스트 항목 간 모순,
이 세션 자체의 히스토리 누락 등 — 전부 수정. doc-check.py ERROR 0 유지.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
2026-08-15 19:36:44 +09:00

7.2 KiB

2026-08-15 두 번째 세션 — 콜백 파라미터 무주석 추론 전방위 재시도, /code-review 2회전 정합성 수정

배경

직전 세션(2026-08-15-01)이 typeof(named fn) 간접참조로 0-Y(반환 타입 leak)를 우회했지만, "콜백 파라미터는 여전히 명시 주석 필요"라는 캐비엇은 그대로 남겨뒀음. 사용자가 이 캐비엇에 대해 "제 손으로도 안 된다는 결론이 나오긴 했지만, 확실하다고 보기는 어려워 보인다"며 type function/메타테이블/제네릭 등 전방위 재시도를 요청 — "확장 시도가 지금 설계에 영향 안 미치더라도 좋다"는 전제로 순수 탐색적 리서치 성격임을 명시.

1차 조사 — type-recursive-issue-try-callback/ 신설 (에이전트 위임, spikes 00~19)

general-purpose 에이전트에 위임(luau 스파이크 다수 작성/실행이라 메인 컨텍스트 보호 목적). 결과:

  • 근본 원인 재정정: 재귀 자기참조 특유의 문제가 아니라 "제네릭이 관여하는 함수 호출 인자로 넘긴 함수 리터럴엔 Luau가 컨텍스트 타입을 전파하지 않는다"는 더 일반적 한계(재귀 없는 Map<T,U>(arr,fn)도 똑같이 샘, 00번).
  • near-miss 두 개 발견, 둘 다 채택 안 함: (a) T를 명시 중간 변수로 먼저 고정하면 재귀형에서도 콜백 파라미터가 정확히 추론됨 (09번), (b) 재사용 가능한 monomorphize 헬퍼 함수를 거치면 T별로 손으로 다시 쓸 필요 없이 같은 효과(18번, 이번 조사의 가장 흥미로운 발견). 둘 다 state:Compute(fn) 단일 호출을 2단계 체인으로 바꿔야만 작동해 typing-limits.md §0 대전제(API를 타입 사정으로 비틀지 않음) 위반으로 기각.
  • type function으로 재귀 반환 자체를 지연 평가하는 시도는 여전히 stack overflow 막다른 길(직전 리서치와 같은 결론 재확인).

base/typing-limits.md §1에 각주로 반영, .claude/README.md audit 색인에 신설 행 추가.

/code-review high 1회전 — 문서 정합성 9건 + 미검증 각도 1건

메인 세션이 직접 검증 후 수정. 대부분 카운트/인용 stale(README audit 개수 7→6, luau-test/STATUS.md 헤딩 7→6, ROADMAP.md M0 체크리스트 미갱신, luau-test-first-run 표의 스파이크 16 결과가 " 실패"로 안 고쳐진 것 등 — 전부 프로젝트 자체 규율인 "배너뿐 아니라 본문도 고칠 것"이 재발한 사례).

핵심 발견 1건: 1차 조사 20개 formulation이 전부 암묵 호출만 시도했고, 코퍼스에 이미 실사용 중인 이중 꺾쇠 명시적 제네릭 인스턴스화(Foo<<T,U>>(...), base/attribute-plan.mdAttributeKey<<T>>, type-recursion-issue/spikes/38/39/41에 이미 있음)를 단 하나도 시도하지 않았음. 메인 세션이 직접 빠르게 재현해보니 quad 실제 self-핸들 계약에서 부분 성공(다운스트림 타입은 정확) + 이전과 다른 spurious 진단(read-only/read-write 불일치)이 섞여 나옴 — 새 각도라 판단해 후속 조사로 위임.

후속 조사 — <<T,U>> 명시적 인스턴스화 (에이전트 위임, spikes 20~33+21b)

  • spurious 진단 원인 규명: 08-metatable-BUG(직전 세션의 solver 버그)와 다른 문제 — duck-typed 콜백 파라미터가 "읽기만 관측돼 read-only로 잡히는데 기대 타입은 기본 read-write"인 단순 가변성 불일치. 쪼개기(②)+Get 필드에 명시 read modifier로 완전 해소됨.
  • leaf 호출에선 실제로 sound하게 성립(체이닝 depth 3까지 완전 클린, 음성 대조군도 정확히 잡힘).
  • partial instantiation은 이름이 아니라 선언 순서로 바인딩U만 주려 해도 T부터 채워짐, 재귀형에서 Internal error로 깨짐(21/21b).
  • 최종 채택 안 함: 매 호출마다 이미 self로부터 결정되는 T까지 중복 명시 + 콜백을 쓰기도 전에 반환 타입 U를 미리 선언해야 하는 부담이 지금 관례("파라미터에 타입 하나만 주석")보다 크고, :Apply 중첩 자기호출은 여전히 안 풀림 — typing-limits.md §0 대전제상 순손해.
  • 함정 하나 추가 발견: 쪼개기+read만 하고 명시 인스턴스화를 뺀 암묵 호출은 겉보기엔 깨끗해 보이지만 음성 대조군을 전혀 못 잡는 순수 duck-typing이었음(27 vs 28 대조).

REPORT.md §11 신설(TL;DR 갱신 포함), typing-limits.md §1엔 각주 포인터만(원칙 자체는 안 바뀜).

/code-review(재사용 high) 2회전 — 정합성 9건

첫 회전이 새로 만든 문서/수정 자체에서 또 발견된 stale — 대부분 "수정하며 새 stale을 만드는" 패턴의 재발:

  • typing-limits.md §1③의 "바뀌지 않는 것" 목록에 옛 솔버 거부 캐비엇이 빠져있던 것(①만 그 캐비엇이 있고 ③엔 없었음) — 추가.
  • §7 체크리스트 항목 1(무조건 ③ 쓰라는 투)과 항목 2(파라미터 추론엔 ②도 같이 필요하다는 헤지)가 서로 부딪히던 것 — 항목 1에 "③은 반환 타입 안전성만 고쳐줌, 파라미터 추론엔 부족" 경고 추가.
  • 이 세션 자체의 작업이 세션 히스토리에 아직 안 남아있던 것 — 이 파일이 그 반영.
  • CLAUDE.md.claude/audit/ 소개 bullet이 3개 폴더만 나열한 채 최신 3개(fallback-xpcall/typeof/try-callback)가 안 반영돼 있던 것 — luau-test bullet과 같은 패턴으로 "나열 안 하고 README.md로 미룸"으로 전환.
  • type-recursion-issue/REPORT.md(0-Y 원래 리서치, 13차 세션 산출물)의 "06/07/10 자유 함수로 빼면 재귀 유무 무관하게 통과" 서술이 실제로는 틀렸음 — 06self: Box<A>처럼 self 자신이 제네릭이라 실제로는 duck-typing 오염 + TypeError가 남(직접 luau-analyze --annotate로 재현 확인). 07/10은 self가 모노몰픽이라 통과 — 진짜 분기점은 "자유 함수인가"가 아니라 "self가 비제네릭인가"였음, 06을 13(self 완전 자유 제네릭도 안 풀림)과 같은 편으로 재분류.
  • 스파이크 개수 off-by-one 2건(README의 try-callback 34→35, 직전 세션 로그의 typeof 폴더 14→15 — 둘 다 서브레터 파일(21b, 00b/00c)을 안 세서 생긴 것).
  • 직전 세션 로그가 "raw 값 콜백 + 50단 체이닝이 깨끗이 통과"를 마치 확정 사실처럼 서술한 것 — 그 formulation은 파일로 저장 안 돼 지금 재현 불가라는 정정 각주 추가(REPORT.md §5-1이 이미 밟은 것과 같은 조치).

남은 것

.claude/README.md(audit 현재 6개 카운트가 커밋 시점 기준으로는 아직 5개였던 것 — 커밋 안 한 현재 작업 트리 기준으로는 이미 6개로 정확해서 별도 조치 없음, 커밋되면 자동 해소), 3건은 code-review가 검증 단계에서 이미 refuted 처리(허위 양성).

관련 문서

.claude/audit/type-recursive-issue-try-callback/(REPORT.md + spikes 35개), base/typing-limits.md §1/§7, .claude/README.md, CLAUDE.md, .claude/audit/type-recursion-issue/REPORT.md, .claude/session/2026-08-15-01-typeof-recursive-generic-workaround.md.