// FIXME: Actually, keep all the failing transformers and put them first, then // finally do the simplest non-failing one. Actually actually, maybe not, since // we're reversing to encode? What's a case where this would be useful? // If there are non-failing transformers, we ignore the ones that can fail and // just pick the "simplest" non-failing one, being the one with the least number // of nodes. function filter(xfers: Transformer[]): Transformer[] { assert(xfers.length > 0, "Must have at least one transformer"); const nonfailing = xfers.filter(xfer => { // For member match transformers we're deciding between // multiple that match against the same member, so the fact // that the match can fail is not important, since if it fails // it will fail for all candidates. The question is whether // its continuation can fail. if (xfer instanceof UnionMemberMatchTransformer) { return !xfer.transformer.canFail; } else { return !xfer.canFail; } }); if (nonfailing.length === 0) return xfers; const smallest = arraySortByInto( nonfailing.map(x => [x.getNumberOfNodes(), x] as [number, Transformer]), ([c, _]) => c )[0][1]; return [smallest]; }
function replaceUnion( union: UnionType, builder: GraphRewriteBuilder<Type>, forwardingRef: TypeRef, transformedTypes: Set<Type>, debugPrintTransformations: boolean ): TypeRef { const graph = builder.typeGraph; assert(union.members.size > 0, "We can't have empty unions"); // Type attributes that we lost during reconstitution. let additionalAttributes = emptyTypeAttributes; function reconstituteMember(t: Type): TypeRef { // Special handling for some transformed string type kinds: The type in // the union must be the target type, so if one already exists, use that // one, otherwise make a new one. if (isPrimitiveStringTypeKind(t.kind)) { const targetTypeKind = targetTypeKindForTransformedStringTypeKind(t.kind); if (targetTypeKind !== undefined) { const targetTypeMember = union.findMember(targetTypeKind); additionalAttributes = combineTypeAttributes("union", additionalAttributes, t.getAttributes()); if (targetTypeMember !== undefined) { return builder.reconstituteType(targetTypeMember); } return builder.getPrimitiveType(targetTypeKind); } } return builder.reconstituteType(t); } const reconstitutedMembersByKind = mapMapEntries(union.members.entries(), m => [m.kind, reconstituteMember(m)]); const reconstitutedMemberSet = new Set(reconstitutedMembersByKind.values()); const haveUnion = reconstitutedMemberSet.size > 1; if (!haveUnion) { builder.setLostTypeAttributes(); } const reconstitutedTargetType = haveUnion ? builder.getUnionType(union.getAttributes(), reconstitutedMemberSet) : defined(iterableFirst(reconstitutedMemberSet)); function memberForKind(kind: TypeKind) { return defined(reconstitutedMembersByKind.get(kind)); } function consumer(memberTypeRef: TypeRef): Transformer | undefined { if (!haveUnion) return undefined; return new UnionInstantiationTransformer(graph, memberTypeRef); } function transformerForKind(kind: TypeKind) { const member = union.findMember(kind); if (member === undefined) return undefined; const memberTypeRef = memberForKind(kind); return new DecodingTransformer(graph, memberTypeRef, consumer(memberTypeRef)); } let maybeStringType: TypeRef | undefined = undefined; function getStringType(): TypeRef { if (maybeStringType === undefined) { maybeStringType = builder.getStringType(emptyTypeAttributes, StringTypes.unrestricted); } return maybeStringType; } function transformerForStringType(t: Type): Transformer | undefined { const memberRef = memberForKind(t.kind); if (t.kind === "string") { return consumer(memberRef); } else if (t instanceof EnumType && transformedTypes.has(t)) { return makeEnumTransformer(graph, t, getStringType(), consumer(memberRef)); } else { return new ParseStringTransformer(graph, getStringType(), consumer(memberRef)); } } const stringTypes = arraySortByInto(Array.from(union.stringTypeMembers), t => t.kind); let transformerForString: Transformer | undefined; if (stringTypes.length === 0) { transformerForString = undefined; } else if (stringTypes.length === 1) { const t = stringTypes[0]; transformerForString = new DecodingTransformer(graph, getStringType(), transformerForStringType(t)); } else { transformerForString = new DecodingTransformer( graph, getStringType(), new ChoiceTransformer(graph, getStringType(), stringTypes.map(t => defined(transformerForStringType(t)))) ); } const transformerForClass = transformerForKind("class"); const transformerForMap = transformerForKind("map"); assert( transformerForClass === undefined || transformerForMap === undefined, "Can't have both class and map in a transformed union" ); const transformerForObject = transformerForClass !== undefined ? transformerForClass : transformerForMap; const transformer = new DecodingChoiceTransformer( graph, builder.getPrimitiveType("any"), transformerForKind("null"), transformerForKind("integer"), transformerForKind("double"), transformerForKind("bool"), transformerForString, transformerForKind("array"), transformerForObject ); const attributes = transformationAttributes(graph, reconstitutedTargetType, transformer, debugPrintTransformations); return builder.getPrimitiveType( "any", combineTypeAttributes("union", attributes, additionalAttributes), forwardingRef ); }