diff --git a/src/Tuple.ts b/src/Tuple.ts new file mode 100644 index 00000000..b17f4a6d --- /dev/null +++ b/src/Tuple.ts @@ -0,0 +1,19 @@ +import { hash, is } from "immutable"; + +import { hashCodeInit, addHashCode } from "./Support"; + +export class Tuple { + constructor(readonly first: T, readonly second: U) {} + + hashCode(): number { + let hashCode = hashCodeInit; + hashCode = addHashCode(hashCode, hash(this.first)); + hashCode = addHashCode(hashCode, hash(this.second)); + return hashCode; + } + + equals(other: any): boolean { + if (!(other instanceof Tuple)) return false; + return is(this.first, other.first) && is(this.second, other.second); + } +} \ No newline at end of file diff --git a/src/UnionBuilder.ts b/src/UnionBuilder.ts index 2f78151b..39a4716e 100644 --- a/src/UnionBuilder.ts +++ b/src/UnionBuilder.ts @@ -2,7 +2,7 @@ import { Map, Set, OrderedMap, OrderedSet } from "immutable"; -import { TypeKind, PrimitiveStringTypeKind, Type, UnionType } from "./Type"; +import { TypeKind, Type, UnionType, Transformation } from "./Type"; import { matchTypeExhaustive } from "./TypeUtils"; import { TypeAttributes, @@ -31,7 +31,7 @@ export interface UnionTypeProvider { readonly lostTypeAttributes: boolean; } -export type TypeAttributeMap = OrderedMap; +export type TypeAttributeMap = OrderedMap; function addAttributes( accumulatorAttributes: TypeAttributes | undefined, @@ -41,23 +41,26 @@ function addAttributes( return combineTypeAttributes(accumulatorAttributes, newAttributes); } -function setAttributes( +function setAttributes( attributeMap: TypeAttributeMap, - kind: T, + key: T, newAttributes: TypeAttributes ): TypeAttributeMap { - return attributeMap.set(kind, addAttributes(attributeMap.get(kind), newAttributes)); + return attributeMap.set(key, addAttributes(attributeMap.get(key), newAttributes)); } -function moveAttributes(map: TypeAttributeMap, fromKind: T, toKind: T): TypeAttributeMap { - const fromAttributes = defined(map.get(fromKind)); - map = map.remove(fromKind); - return setAttributes(map, toKind, fromAttributes); +function moveAttributes(map: TypeAttributeMap, fromKey: T, toKey: T): TypeAttributeMap { + const fromAttributes = defined(map.get(fromKey)); + map = map.remove(fromKey); + return setAttributes(map, toKey, fromAttributes); } export class UnionAccumulator implements UnionTypeProvider { private _nonStringTypeAttributes: TypeAttributeMap = OrderedMap(); - private _stringTypeAttributes: TypeAttributeMap = OrderedMap(); + // Once enums are not allowed anymore, this goes to undefined. + // Invariant: _enumTypeAttributes === undefined || _stringTypeAttributes.isEmpty() + private _enumTypeAttributes: TypeAttributes | undefined = Map(); + private _stringTypeAttributes: TypeAttributeMap = OrderedMap(); readonly arrayData: TArray[] = []; readonly objectData: TObject[] = []; @@ -71,9 +74,15 @@ export class UnionAccumulator implements UnionTypeProvider implements UnionTypeProvider implements UnionTypeProvider, attributes: TypeAttributes): void { if (this.have("string")) { - this.addStringType("string", attributes); + this.addStringType(undefined, attributes); return; } + if (this._enumTypeAttributes === undefined) { + return panic("State machine screwed up"); + } cases.forEach((count, s) => { if (!Object.prototype.hasOwnProperty.call(this.enumCaseMap, s)) { @@ -143,23 +148,46 @@ export class UnionAccumulator implements UnionTypeProvider { - let merged = this._nonStringTypeAttributes.merge(this._stringTypeAttributes); + getMemberKinds(): OrderedMap { + let merged = this._nonStringTypeAttributes.map(ta => [undefined, ta] as [Transformation | undefined, TypeAttributes]); + if (this._enumTypeAttributes !== undefined) { + assert(this._stringTypeAttributes.isEmpty(), "State machine screwed up"); + if (this.enumCases.length > 0) { + merged = merged.set("enum", [undefined, this._enumTypeAttributes]); + } else { + assert(this._enumTypeAttributes.size === 0, "How do we have enum type attributes but no cases?"); + } + } + if (this._stringTypeAttributes.size > 0) { + assert(this._enumTypeAttributes === undefined, "State machine screwed up"); + const combinedAttributes = combineTypeAttributes(this._stringTypeAttributes.valueSeq().toArray()); + const transformations = this._stringTypeAttributes.keySeq().toSet(); + let transformation: Transformation | undefined; + if (transformations.has(undefined)) { + transformation = undefined; + } else if (transformations.size === 1) { + transformation = defined(transformations.first()); + } else { + transformation = unionOfTransformations("string", transformations); + } + merged = merged.set("string", [transformation, combinedAttributes]); + } + if (merged.isEmpty()) { - return OrderedMap([["none", Map()] as [TypeKind, TypeAttributes]]); + return OrderedMap([["none", [undefined, Map()]] as [TypeKind, [Transformation | undefined, TypeAttributes]]]); } if (this._nonStringTypeAttributes.has("any")) { assert(this._lostTypeAttributes, "This had to be set when we added 'any'"); - const allAttributes = combineTypeAttributes(merged.valueSeq().toArray()); - return OrderedMap([["any", allAttributes] as [TypeKind, TypeAttributes]]); + const allAttributes = combineTypeAttributes(merged.valueSeq().toArray().map(([_, ta]) => ta)); + return OrderedMap([["any", [undefined, allAttributes]] as [TypeKind, [Transformation | undefined, TypeAttributes]]]); } if (this._conflateNumbers && this.have("integer") && this.have("double")) {