Example #1
0
        // 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];
        }
Example #2
0
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
    );
}