{"record":{"id":"0c856b98ba38cefd","repo":"can1357/oh-my-pi","slug":"parseerror-an-unordered-union-of-a-type-including","errorCode":null,"errorMessage":"ParseError: An unordered union of a type including a morph and a type with overlapping input is indeterminate:\\nLeft: ${leftExpression}\\nRight: ${rightExpression}","messagePattern":"ParseError: An unordered union of a type including a morph and a type with overlapping input is indeterminate:\\\\nLeft: (.+?)\\\\nRight: (.+?)","errorType":"exception","errorClass":"OmpTypeError","httpStatus":null,"severity":"error","filePath":"packages/omptype/src/type.ts","lineNumber":979,"sourceCode":"\t\treturn inheritScope(this, makeType(distributeFilter(this.ir, parseDef(def, this.resolver), false), [], {}));\n\t},\n\n\tonUndeclaredKey(this: InternalType, behavior: \"ignore\" | \"reject\" | \"delete\"): InternalType {\n\t\tconst extras = behavior === \"ignore\" ? \"keep\" : behavior;\n\t\tconst ir = withShallowExtras(this.ir, extras);\n\t\tif (extras === \"delete\" && ir.k === \"union\") {\n\t\t\tconst objects = ir.members.filter((member): member is ObjectIR => member.k === \"object\");\n\t\t\tfor (let left = 0; left < objects.length; left++) {\n\t\t\t\tfor (let right = left + 1; right < objects.length; right++) {\n\t\t\t\t\tconst sharedRequired = objects[left].props.some(\n\t\t\t\t\t\tleftProp =>\n\t\t\t\t\t\t\t!leftProp.opt &&\n\t\t\t\t\t\t\tobjects[right].props.some(rightProp => !rightProp.opt && rightProp.key === leftProp.key),\n\t\t\t\t\t);\n\t\t\t\t\tif (!sharedRequired) {\n\t\t\t\t\t\tconst leftExpression = expressionOf(objects[left]).replace(/ }$/, \", + (undeclared): delete }\");\n\t\t\t\t\t\tconst rightExpression = expressionOf(objects[right]).replace(/ }$/, \", + (undeclared): delete }\");\n\t\t\t\t\t\tthrow new OmpTypeError(\n\t\t\t\t\t\t\t`ParseError: An unordered union of a type including a morph and a type with overlapping input is indeterminate:\\nLeft: ${leftExpression}\\nRight: ${rightExpression}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn makeType(ir, this[kSteps], metaOf(this));\n\t},\n\n\tonDeepUndeclaredKey(this: InternalType, behavior: \"ignore\" | \"reject\" | \"delete\"): InternalType {\n\t\treturn makeType(withDeepExtras(this.ir, behavior === \"ignore\" ? \"keep\" : behavior), this[kSteps], metaOf(this));\n\t},\n\n\tallows(this: InternalType, data: unknown): data is unknown {\n\t\tconst steps = this[kSteps];\n\t\tlet needsPredicates = false;\n\t\tfor (const step of steps) {\n\t\t\tif (step.kind !== \"pipe\") {","sourceCodeStart":961,"sourceCodeEnd":997,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/omptype/src/type.ts#L961-L997","documentation":"When unioning a type containing a morph (input-transforming function) with another object type whose input overlaps, omptype cannot decide which branch to try first at parse time, making the union indeterminate. The error shows both branches rewritten with `, + (undeclared): delete` to expose their effective input types. Adding a required shared key on the non-morph branch is what disambiguates, via the `sharedRequired` check.","triggerScenarios":"`type(morphType).or(objectType)` (or `type([a, b])`) where both sides accept the same input shape — e.g. `type('string.numeric.parse').or('number')`-style overlap at the object level, or two objects both accepting `{a: string}` where one has a morph and no distinguishing required key.","commonSituations":"Versioned payload parsing (`v1|v2` where shapes overlap); unioning a coerced/transformed object with a plain object schema; migrating schemas so a previously distinct branch became overlapping.","solutions":["Add a distinguishing required property to one branch so dispatch is deterministic.","Make the union ordered if your usage supports it (e.g. pipe with explicit precedence) instead of an unordered `or`.","Restrict one branch's input (narrow its props/types) so inputs no longer overlap.","Handle the two shapes in separate parse attempts with explicit fallback logic."],"exampleFix":"// before\ntype({ id: 'string.parse' }).or({ id: 'string', kind: 'string' }); // indeterminate\n\n// after\n// 'kind' is required on the right branch, disambiguating dispatch\ntype({ id: 'string.parse' }).or({ id: 'string', kind: 'string' });\n// or narrow the morph branch input so they don't overlap","handlingStrategy":"validation","validationCode":"// ensure at least one required key differentiates the branches before unioning\nfunction branchesDisjoint(a, b) {\n  const aKeys = Object.keys(a.shape ?? {});\n  const bKeys = Object.keys(b.shape ?? {});\n  return aKeys.some(k => !bKeys.includes(k)) || bKeys.some(k => !aKeys.includes(k));\n}","typeGuard":null,"tryCatchPattern":"try {\n  const U = morphType.or(plainType);\n} catch (e) {\n  if (e instanceof OmpTypeError && e.message.includes('indeterminate')) {\n    // fall back to sequential attempts: morphType.run first, then plainType.run\n  } else throw e;\n}","preventionTips":["Design union branches with a required discriminator key (kind/type/version).","Prefer ordered parse attempts over unordered `or` when morphs are involved.","Add a build-time check that renders every union to catch indeterminate cases early."],"tags":["parse-error","union","morph","type-error"],"backgroundTag":"indeterminate-union-with-morph","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}