can1357/oh-my-pi · error · OmpTypeError

optional "?" marker is only valid on object property values

Error message

optional "?" marker is only valid on object property values

What it means

The "?" optional marker is only meaningful on object property values, where it marks the key as possibly absent. If parseStringDef returns parsed.optional for a definition parsed at top level (not as an object property value), parseDef rejects it because there is no enclosing key to make optional.

Source

Thrown at packages/omptype/src/ir.ts:1500

		index: indexes?.string,
		symbolIndex: indexes?.symbol,
		patternIndexes: indexes === undefined || indexes.patterns.length === 0 ? undefined : indexes.patterns,
		extras,
	};
	object[kSimple] = simple;
	object[kSimpleOwner] = object;
	return object;
}

/** Parse a definition, optionally resolving names from an enclosing scope. */
export function parseDef(def: unknown, resolve?: AliasResolver): IR {
	if (typeof def === "string") {
		const parsed = parseStringDef(def, resolve);
		if (parsed.hasDefault) {
			throw new OmpTypeError("A default may only be specified for an object property or tuple element");
		}
		if (parsed.optional) {
			throw new OmpTypeError(`optional "?" marker is only valid on object property values`);
		}
		return parsed.ir;
	}
	if (Array.isArray(def)) {
		if (def.length === 3 && def[1] === "=") {
			throw new OmpTypeError("A default may only be specified for an object property or tuple element");
		}
		return parseArrayExpression(def, resolve);
	}
	if (def instanceof RegExp) return patternIR(def);
	if (def instanceof Date) return { k: "lit", v: def };
	if (isEmbedded(def)) return embed(def);
	if (typeof def === "function") {
		const resolved = Reflect.apply(def, undefined, []);
		if (!isEmbedded(resolved)) {
			throw new OmpTypeError(`thunk must return a Type (was ${typeof resolved})`);
		}
		return embed(resolved);

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the "?" marker from the standalone definition
  2. Move the definition back into an object property position where "?" is valid
  3. Express the optionality at the consumer (e.g. a union with undefined) if it must be standalone

Example fix

// before
const T = type("name?")
// after
const T = type({ "name?": "string" })
Defensive patterns

Strategy: type-guard

Validate before calling

function assertNoTopLevelOptional(def) {
  if (typeof def === "string" && def.endsWith("?"))
    throw new Error('"?" is only valid on object property values');
}

Type guard

const isOptionalMarker = (d) => typeof d === "string" && d.endsWith("?");

Try / catch

try { const T = type(def); } catch (e) {
  if (e.message.includes('optional "?" marker')) {
    // strip the "?" or relocate into an object definition
  } else throw e;
}

Prevention

When it happens

Trigger: Calling parseDef/type() with a standalone string such as "name?" or an array expression element containing the optional marker — any def that reaches the typeof def === "string" branch of parseDef with parsed.optional true.

Common situations: Copy-pasting a property string like "age?: number-ish" out of an object definition into a standalone type; building alias bodies or tuple items with "?" markers; template strings that leaked the "?" suffix.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/82ddb44264debbad. Report an issue: GitHub.