From d04ccb3f1f10f3dcc788af010be7394bc67b2c8c Mon Sep 17 00:00:00 2001 From: Gideon Wald Date: Tue, 4 Nov 2025 14:05:05 -0800 Subject: [PATCH 1/3] small script showcasing typechecking issue with CELL_BRAND mechanism in case of OpaqueRef> --- cell-brand-bug-reproduction.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 cell-brand-bug-reproduction.ts diff --git a/cell-brand-bug-reproduction.ts b/cell-brand-bug-reproduction.ts new file mode 100644 index 000000000..28d82c278 --- /dev/null +++ b/cell-brand-bug-reproduction.ts @@ -0,0 +1,24 @@ +/** + * Minimal test case for OpaqueRef> CELL_BRAND conflict + * + * PROBLEM: OpaqueRef recursively wraps ALL properties of T (including [CELL_BRAND]). + * When T is Cell, this creates conflicting types for [CELL_BRAND]: + * - OpaqueCell> has [CELL_BRAND]: "opaque" + * - { counter: OpaqueRef> } creates [CELL_BRAND]: OpaqueRef<"cell"> + * The intersection reduces to 'never'. + */ + +import { type Cell, type OpaqueRef } from "./packages/api/index.ts"; + +interface State { + counter: Cell; +} + +// Accessing properties on OpaqueRef> fails type-checking +function reproducesBug(state: OpaqueRef) { + state.counter.set(state.counter.get() + 1); + // ^^^ Property 'set' does not exist on type 'never' + // ^^^ Property 'get' does not exist on type 'never' + // ERROR: The intersection 'OpaqueRef>' was reduced to 'never' + // because property '[CELL_BRAND]' has conflicting types in some constituents. +} From 3771aa9a1e95a6d69fa156eebd141c790b0594fd Mon Sep 17 00:00:00 2001 From: Gideon Wald Date: Tue, 4 Nov 2025 14:24:10 -0800 Subject: [PATCH 2/3] attempt to fix CELL_BRAND issue --- packages/api/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/api/index.ts b/packages/api/index.ts index 8d7c7331b..51f90c183 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -352,11 +352,18 @@ export interface WriteonlyCell * OpaqueRef is a variant of OpaqueCell with recursive proxy behavior. * Each key access returns another OpaqueRef, allowing chained property access. * This is temporary until AST transformation handles .key() automatically. + * + * Note: Symbol keys (like CELL_BRAND) are excluded from mapping to avoid + * brand conflicts. Already-branded cells are preserved as-is rather than + * being wrapped again to prevent cell-of-cell situations. */ export type OpaqueRef = & OpaqueCell & (T extends Array ? Array> - : T extends object ? { [K in keyof T]: OpaqueRef } + : T extends object ? { + [K in keyof T as K extends symbol ? never : K]: + T[K] extends BrandedCell ? T[K] : OpaqueRef + } : T); // ============================================================================ From dd12a0de68110ced6e6a96fb201da7f80587a5f4 Mon Sep 17 00:00:00 2001 From: Gideon Wald Date: Tue, 4 Nov 2025 14:34:30 -0800 Subject: [PATCH 3/3] next attempt --- cell-brand-bug-reproduction.ts | 2 +- packages/api/index.ts | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/cell-brand-bug-reproduction.ts b/cell-brand-bug-reproduction.ts index 28d82c278..13a90ff03 100644 --- a/cell-brand-bug-reproduction.ts +++ b/cell-brand-bug-reproduction.ts @@ -15,7 +15,7 @@ interface State { } // Accessing properties on OpaqueRef> fails type-checking -function reproducesBug(state: OpaqueRef) { +function _reproducesBug(state: OpaqueRef) { state.counter.set(state.counter.get() + 1); // ^^^ Property 'set' does not exist on type 'never' // ^^^ Property 'get' does not exist on type 'never' diff --git a/packages/api/index.ts b/packages/api/index.ts index 51f90c183..668cf0c99 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -354,16 +354,12 @@ export interface WriteonlyCell * This is temporary until AST transformation handles .key() automatically. * * Note: Symbol keys (like CELL_BRAND) are excluded from mapping to avoid - * brand conflicts. Already-branded cells are preserved as-is rather than - * being wrapped again to prevent cell-of-cell situations. + * brand conflicts when wrapping branded cells. */ export type OpaqueRef = & OpaqueCell & (T extends Array ? Array> - : T extends object ? { - [K in keyof T as K extends symbol ? never : K]: - T[K] extends BrandedCell ? T[K] : OpaqueRef - } + : T extends object ? { [K in keyof T as K extends symbol ? never : K]: OpaqueRef } : T); // ============================================================================