toeverything/AFFiNE · error · BlockSuiteError

DatabaseBlockError

DatabaseBlockError

Error message

unexpected type unify, type var reference

What it means

Internal invariant of the data-view logical type engine. TypeVarReferenceInstance (type-variable.ts:20-45) is a placeholder produced by tRef(name); it must be substituted with a concrete TypeInstance via subst(TypeVarContext) before type unification. Its unify() always throws 'unexpected type unify, type var reference', meaning a type variable was never bound (its name missing from the context) when unification ran.

Solutions

  1. Ensure every tVarDefine name referenced by tRef has an entry in the TypeVarContext before unifying
  2. Call subst first, verify it returned a concrete type, and only then unify
  3. Use the built-in guard tv.typeVarReference.is(type) to reject placeholders before calling unify

Example fix

// before
const ok = unify(ctx, tRef('T'), concreteType); // throws if 'T' unbound

// after
const resolved = substitute(tRef('T'), ctx);
if (!resolved) throw new Error(`unbound type variable T`);
Defensive patterns

Strategy: type-guard

Validate before calling

import { tv } from './type-variable.js';

// substitute placeholders and verify binding before unify
const substResult = typeInstance.subst(typeVarContext);
if (tv.typeVarReference.is(typeInstance) && !substResult) {
  throw new Error(`unbound type variable: ${typeInstance.varName}`);
}

Type guard

tv.typeVarReference.is = (type: TypeInstance): type is TypeVarReferenceInstance =>
  type.name === '__TypeVarReference';

// usage: never call unify on a value passing this guard

Try / catch

try {
  unify(ctx, left, right);
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === BlockSuiteError.ErrorCode.DatabaseBlockError) {
    // binding bug: report which type var was left unsubstituted
  }
  throw e;
}

Prevention

When it happens

Trigger: Unifying a TypeInstance that is still a __TypeVarReference because ctx[varName] was never defined (subst returned undefined); building logical expressions where tRef('x') has no matching tVar define entry in the TypeVarContext; calling unify directly on a freshly created tRef without substitution.

Common situations: Writing custom logical expressions / property type constraints against the data-view type engine; refactoring type-variable plumbing so define and reference names diverge; serialize/deserialize of expressions losing the define side of the context.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/93d228ef60f15f92. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/affine/data-view/src/core/logical/type-variable.ts:36

}

export class TypeVarReferenceInstance<
  Name extends string = string,
> implements TypeInstance {
  readonly _validate = unknownSchema;

  readonly _valueType = undefined as unknown;

  readonly name = '__TypeVarReference';

  constructor(readonly varName: Name) {}

  subst(ctx: TypeVarContext): void | TypeInstance {
    return ctx[this.varName]?.type;
  }

  unify(_ctx: TypeVarContext, _type: TypeInstance, _unify: Unify): boolean {
    throw new BlockSuiteError(
      ErrorCode.DatabaseBlockError,
      'unexpected type unify, type var reference'
    );
  }

  valueValidate(_value: unknown): _value is unknown {
    return true;
  }
}

export const tv = {
  typeVarDefine: {
    create: <
      Name extends string = string,
      Type extends TypeInstance = TypeInstance,
    >(
      name: Name,
      typeConstraint?: Type

View on GitHub (pinned to b4c8548c09)