diff --git a/codegen/common.ts b/codegen/common.ts index d0e840e..2feb4b9 100644 --- a/codegen/common.ts +++ b/codegen/common.ts @@ -18,10 +18,15 @@ export interface IPartialOperatorSpec { export interface IOperatorSpec extends ICommonSpec, IPartialOperatorSpec {} -export function getExponents({ minExponent, maxExponent }: ICommonSpec): number[] { - const exponents: number[] = []; - for (let exponent = minExponent; exponent <= maxExponent; exponent++) { - exponents.push(exponent); +export interface IExponentSpec { + value: number; + type: string; +} + +export function getExponents({ minExponent, maxExponent }: ICommonSpec): IExponentSpec[] { + const exponents: IExponentSpec[] = []; + for (let value = minExponent; value <= maxExponent; value++) { + exponents.push({ value, type: `"${value}"` }); } return exponents; } @@ -46,3 +51,11 @@ export function genUncurriedTypeName(spec: IOperatorSpec, left?: string | number const args = left !== undefined && right !== undefined ? `<${left}, ${right}>` : ""; return `${spec.uncurriedTypeNamePrefix}Exponents${args}`; } + +export function genExponentName({ value }: IExponentSpec): string { + if (value === 0) { + return "Zero"; + } + const sign = value < 0 ? "Neg" : "Pos"; + return `${sign}${Math.abs(value)}`; +} diff --git a/codegen/genExponent.ts b/codegen/genExponent.ts index 4a39986..52af8c5 100644 --- a/codegen/genExponent.ts +++ b/codegen/genExponent.ts @@ -1,6 +1,8 @@ import { genFileHeader, getExponents, ICommonSpec } from "./common"; export function genExponentType(spec: ICommonSpec): string { - const exponents = getExponents(spec).join(" | "); + const exponents = getExponents(spec) + .map(exponent => exponent.type) + .join(" | "); return [...genFileHeader(false), `export type Exponent = ${exponents};`, ""].join("\n"); } diff --git a/codegen/genTests.ts b/codegen/genTests.ts index b7aa68d..5e7ae50 100644 --- a/codegen/genTests.ts +++ b/codegen/genTests.ts @@ -1,4 +1,13 @@ -import { genFileHeader, genImport, genUncurriedTypeName, getExponents, IOperatorSpec, isExponent } from "./common"; +import { + genExponentName, + genFileHeader, + genImport, + genUncurriedTypeName, + getExponents, + IExponentSpec, + IOperatorSpec, + isExponent, +} from "./common"; export function genOperatorTests(spec: IOperatorSpec): string { return [ @@ -20,18 +29,10 @@ function genTests(spec: IOperatorSpec): string[] { return lines; } -function genTest(spec: IOperatorSpec, left: number, right: number): string { - const result = spec.compute(left, right); - const typeName = `${spec.testTypeNamePrefix}Of${genValueName(left)}And${genValueName(right)}`; - const testType = `${genUncurriedTypeName(spec, left, right)}`; - const expectedType = isExponent(result, spec) ? `${result}` : "never"; +function genTest(spec: IOperatorSpec, left: IExponentSpec, right: IExponentSpec): string { + const result = spec.compute(left.value, right.value); + const typeName = `${spec.testTypeNamePrefix}Of${genExponentName(left)}And${genExponentName(right)}`; + const testType = `${genUncurriedTypeName(spec, left.type, right.type)}`; + const expectedType = isExponent(result, spec) ? `"${result}"` : "never"; return `type ${typeName} = ${testType}; // $ExpectType ${expectedType}`; } - -function genValueName(value: number): string { - if (value === 0) { - return "Zero"; - } - const sign = value < 0 ? "Neg" : "Pos"; - return `${sign}${Math.abs(value)}`; -} diff --git a/codegen/genTypes.ts b/codegen/genTypes.ts index 5706469..3566e5a 100644 --- a/codegen/genTypes.ts +++ b/codegen/genTypes.ts @@ -1,43 +1,67 @@ -import { genFileHeader, genImport, genUncurriedTypeName, getExponents, IOperatorSpec, isExponent } from "./common"; +import { + genExponentName, + genFileHeader, + genImport, + genUncurriedTypeName, + getExponents, + IExponentSpec, + IOperatorSpec, + isExponent, +} from "./common"; export function genOperatorTypes(spec: IOperatorSpec): string { + const exponents = getExponents(spec); return [ ...genFileHeader(), ...genImport("Exponent", "./exponent"), - ...genUncurriedType(spec, getExponents(spec)), + ...genUncurriedType(spec), + ...genUncurriedTable(spec, exponents), + ...genAllCurriedTables(spec, exponents), ].join("\n"); } -function genUncurriedType(spec: IOperatorSpec, exponents: number[]): string[] { - const lines = [`export type ${genUncurriedTypeName(spec, "L extends Exponent", "R extends Exponent")}`]; - let first = true; +function genUncurriedType(spec: IOperatorSpec): string[] { + const typeName = genUncurriedTypeName(spec, "L extends Exponent", "R extends Exponent"); + const tableName = genUncurriedTableName(spec); + return [`export type ${typeName} = ${tableName}[L][R];`, ""]; +} + +function genUncurriedTable(spec: IOperatorSpec, exponents: IExponentSpec[]): string[] { + const name = genUncurriedTableName(spec); + const lines = [`interface ${name} {`]; + for (const left of exponents) { + lines.push(indent(`${left.type}: ${genCurriedTableName(spec, left)};`)); + } + lines.push("}", ""); + return lines; +} + +function genUncurriedTableName(spec: IOperatorSpec): string { + return `I${spec.uncurriedTypeNamePrefix}Table`; +} + +function genAllCurriedTables(spec: IOperatorSpec, exponents: IExponentSpec[]): string[] { + const lines: string[] = []; for (const left of exponents) { - const operator = first ? "=" : ":"; - const prefix = indent(`${operator} L extends ${left} ?`); - first = false; - if (left in spec.specialCases) { - lines.push(`${prefix} ${spec.specialCases[left]}`); - } else { - lines.push(`${prefix}`); - lines.push(...genCurriedType(spec, exponents, left)); - } + lines.push(...genCurriedTable(spec, exponents, left)); } - lines.push(indent(`: never;`)); - lines.push(""); return lines; } -function genCurriedType(spec: IOperatorSpec, exponents: number[], left: number): string[] { - const lines = ["("]; +function genCurriedTable(spec: IOperatorSpec, exponents: IExponentSpec[], left: IExponentSpec): string[] { + const name = genCurriedTableName(spec, left); + const lines = [`interface ${name} {`]; for (const right of exponents) { - const result = spec.compute(left, right); - if (isExponent(result, spec)) { - lines.push(indent(`R extends ${right} ? ${result} :`)); - } + const result = spec.compute(left.value, right.value); + const value = isExponent(result, spec) ? `"${result}"` : "never"; + lines.push(indent(`${right.type}: ${value};`)); } - lines.push(indent("never")); - lines.push(")"); - return lines.map(indent).map(indent); + lines.push("}", ""); + return lines; +} + +function genCurriedTableName(spec: IOperatorSpec, left: IExponentSpec): string { + return `I${spec.curriedTypeNamePrefix}${genExponentName(left)}Table`; } function indent(line: string): string { diff --git a/docs/builtin.md b/docs/builtin.md index 877a741..efc73d8 100644 --- a/docs/builtin.md +++ b/docs/builtin.md @@ -10,7 +10,7 @@ The built-in units include the standard set of SI base dimension and correspondi * `Length` / `meters` * `Mass` / `kilograms` -* `Time` `seconds` +* `Time` / `seconds` * `ElectricCurrent` / `amperes` * `Temperature` / `kelvin` * `AmountOfSubstance` / `moles` diff --git a/docs/defining-quantities.md b/docs/defining-quantities.md index 244aee3..63c2121 100644 --- a/docs/defining-quantities.md +++ b/docs/defining-quantities.md @@ -1,6 +1,6 @@ # Defining Quantities -We define a **quantity** as the type of a measurement. Length, time, force, velocity, and pressure are all examples of quantities. Safe Units contains many built in quantities so we can write unit safe code easily: +We define a **quantity** as the type of a measurement. Length, time, force, velocity, and pressure are all examples of quantities. Safe Units contains many built in quantities so we can easily write unit safe code: ```ts import { Acceleration, Time, Velocity } from "safe-units"; diff --git a/docs/generic-measures.md b/docs/generic-measures.md index cc3208e..3817ae3 100644 --- a/docs/generic-measures.md +++ b/docs/generic-measures.md @@ -56,7 +56,7 @@ type Measure = IGenericMeasure; After we've defined the type of `WrappedMeasure` we now define the class itself by calling `createMeasureType`. This function takes an object which let's the generic measure type know how to perform operations on our numeric type. Note that for this simple example, we generally just unwrap the value, perform the arithmetic operation and then wrap it back up. Most of these operations should be self-explanatory, however some require some further explanation: - `one`: A function with no arguments that simply returns the 1 value or multiplicative identity of your number system. This is used to construct base units whose values are implicitly one. -- `pow`: This function is slightly different from the rest of the arithmetic operations in that it doesn't take to values of type `N`, instead its signature is: `pow: (base: N, power: Exponent) => N` where `Exponent` is the union of `-5 | -4 | ... | 4 | 5`. This is due to the computational limitations of the library that we need to be specific in the kinds of exponents we can handle. +- `pow`: This function is slightly different from the rest of the arithmetic operations in that it doesn't take to values of type `N`, instead its signature is: `pow: (base: N, power: number) => N`. The `power` argument is a JavaScript `number` but will only ever be an integer between -5 and 5, inclusive. This is due to the computational limitations of the library that we need to be specific in the kinds of exponents we can handle. - `compare`: A function that returns a negative `number` if its first argument is less than its second, a positive `number` if its first argument is greater than its second, and `0` if the arguments are equal. ## Usage @@ -69,7 +69,7 @@ By default, generic measures come with a set of static methods that can be appli ```ts declare function foo(value: WrappedNumber): WrappedNumber; -declare const mass: WrappedMeasure<{ mass: 1 }>; +declare const mass: WrappedMeasure<{ mass: "1" }>; const WrappedMeasure = createMeasureType({ ... }, { foo: wrapUnaryFn(foo), diff --git a/docs/limitations.md b/docs/limitations.md index 2d146c3..607d6da 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -1,6 +1,6 @@ # Limitations -Since Safe Units is typesafe, it must compute units at compile time. Due to some technical limitations of what you can do with types in TypeScript, there is one major limitation to this library: The exponents for dimensions of your units are limited to integers between -5 and 5 (inclusive). This means that you can not represent a value of 30 m6 in this library (though, why would you?). +Since Safe Units is typesafe, it must compute units at compile time. Due to some technical limitations of what you can do with types in TypeScript, there is one major limitation to this library: The exponents for dimensions of your units are limited to integers between -5 and 5, inclusive. (However, under the hood these are represented by string literals between `"-5"` and `"5"`). This means that you can not represent a value of 30 m6 in this library (though, why would you?). In the research I've conducted for this library I cannot find any instances in which it would be useful to use units with such extreme exponents. If you're aware of any such use cases, please [file an issue](https://github.com/jscheiny/safe-units/issues/new) to discuss it. @@ -12,7 +12,7 @@ If two units will multiply or divide together to create an exponent out of range ```ts const a = Measure.of(1, meters.cubed()); -const b = Measure.of(1, meters.toThe(-3)); +const b = Measure.of(1, meters.toThe("-3")); const product = a.times(a); // ~ Error: The result would have unit m^6 const quotient = a.over(b); @@ -29,6 +29,6 @@ const a = m.squared(); // ~~~~~~~~~~~ Error: squared has type never const b = m.cubed(); // ~~~~~~~~~ Error: cubed has type never -const c = m.toThe(3); -// ~ Error: m cannot be cubed so 3 is an invalid argument +const c = m.toThe("3"); +// ~~~ Error: m cannot be cubed so "3" is an invalid argument ``` diff --git a/docs/measures.md b/docs/measures.md index a640cae..a1a3bd3 100644 --- a/docs/measures.md +++ b/docs/measures.md @@ -199,11 +199,13 @@ const doubledLong = t.times(Measure.dimensionless(2)); Measure.toThe(exponent: E): Measure> Measure.pow(measure: Measure, exponent: E): Measure> -Measure.squared(): Measure> -Measure.cubed(): Measure> +Measure.squared(): Measure> +Measure.cubed(): Measure> ``` -The first two methods raise a measure's value and unit to a given exponent (within a limited range). The last two methods, `squared` and `cubed`, are convenience methods for `measure.toThe(2)` and `measure.toThe(3)` respectively. +The first two methods raise a measure's value and unit to a given exponent (within a limited range). The last two methods, `squared` and `cubed`, are convenience methods for `measure.toThe("2")` and `measure.toThe("3")` respectively. + +Note that the exponents passed in are string literals and not numbers. *Examples:* @@ -213,7 +215,7 @@ const side = Measure.of(10, meters); const area: Area = side.squared(); // 100 m^2 const volume: Volume = side.cubed(); // 1000 m^3 -const s: Length = volume.toThe(-3); // 10 m +const s: Length = volume.toThe("-3"); // 10 m ``` **Note:** There are limitations on what measures you may exponentiate. See [Limitations](Limitations). @@ -221,11 +223,11 @@ const s: Length = volume.toThe(-3); // 10 m ### Reciprocals ```ts -Measure.inverse(): Measure> -Measure.reciprocal(): Measure> +Measure.inverse(): Measure> +Measure.reciprocal(): Measure> ``` -Computes the reciprocal of the value and unit of the measure. Both methods are identical and equivalent to `measure.toThe(-1)`. +Computes the reciprocal of the value and unit of the measure. Both methods are identical and equivalent to `measure.toThe("-1")`. *Examples:* @@ -357,7 +359,7 @@ Measure.unsafeMap( ): Measure; ``` -If only one argument is passed, performs a mapping on the value of a measure without affecting the unit of the measure. If both arguments are passed maps both the unit and value of a measure. This is generally used for internal purposes and should be avoided when possible. Instead consider using a [function wrapper](#function-wrappers). +If only one argument is passed, performs a mapping on the value of a measure without affecting the unit of the measure. If both arguments are passed maps both the unit and value of a measure. This is generally used for internal purposes and should be avoided whenever possible. Instead consider using a [function wrapper](#function-wrappers). ## Representation diff --git a/package.json b/package.json index bf531e5..c6a6515 100644 --- a/package.json +++ b/package.json @@ -57,12 +57,12 @@ "rimraf": "^2.6.3", "ts-jest": "^22.4.6", "ts-node": "^6.0.3", - "tslint": "^5.10.0", + "tslint": "^5.18.0", "tslint-config-prettier": "^1.12.0", "tslint-config-standard": "^7.0.0", "tslint-no-circular-imports": "^0.4.0", "tslint-plugin-prettier": "^1.3.0", - "typescript": "~2.9.1", + "typescript": "~3.5.2", "typestyle": "^2.0.1" } } diff --git a/src/exponent/exponentArithmetic.ts b/src/exponent/exponentTypeArithmetic.ts similarity index 83% rename from src/exponent/exponentArithmetic.ts rename to src/exponent/exponentTypeArithmetic.ts index 8990982..15b5db8 100644 --- a/src/exponent/exponentArithmetic.ts +++ b/src/exponent/exponentTypeArithmetic.ts @@ -14,9 +14,9 @@ export type AddendOf = SubtractExponents = AddendOf>; /** Exponents that can be multiplied with N without producing an error. */ -export type MultiplicandOf = 0 extends N ? Exponent : DivideExponents>; +export type MultiplicandOf = "0" extends N ? Exponent : DivideExponents>; /** Exponents that are non-error multiples of N. */ export type ProductOf = MultiplyExponents; -type Negative = MultiplyExponents; +type Negative = MultiplyExponents; diff --git a/src/exponent/exponentValueArithmetic.ts b/src/exponent/exponentValueArithmetic.ts new file mode 100644 index 0000000..5089aa9 --- /dev/null +++ b/src/exponent/exponentValueArithmetic.ts @@ -0,0 +1,20 @@ +import { Exponent } from "./generated/exponent"; + +export function getExponentValue(value: Exponent): number { + return parseInt(value, 10); +} + +export const negateExponent = (value: Exponent) => toExponent(-getExponentValue(value)); +export const addExponents = wrapBinaryExponentFn((left, right) => left + right); +export const multiplyExponents = wrapBinaryExponentFn((left, right) => left * right); +export const divideExponents = wrapBinaryExponentFn((left, right) => left / right); + +type BinaryExponentFn = (left: Exponent, right: Exponent) => Exponent; + +function wrapBinaryExponentFn(fn: (left: number, right: number) => number): BinaryExponentFn { + return (left, right) => toExponent(fn(getExponentValue(left), getExponentValue(right))); +} + +function toExponent(value: number): Exponent { + return `${value}` as Exponent; +} diff --git a/src/exponent/index.ts b/src/exponent/index.ts index 13a6271..6bc036a 100644 --- a/src/exponent/index.ts +++ b/src/exponent/index.ts @@ -1,4 +1,5 @@ -export * from "./exponentArithmetic"; +export * from "./exponentTypeArithmetic"; +export * from "./exponentValueArithmetic"; export * from "./generated/addition"; export * from "./generated/division"; export * from "./generated/exponent"; diff --git a/src/measure/__test__/numberMeasureTests.ts b/src/measure/__test__/numberMeasureTests.ts index bec1fda..afb6aac 100644 --- a/src/measure/__test__/numberMeasureTests.ts +++ b/src/measure/__test__/numberMeasureTests.ts @@ -9,7 +9,7 @@ describe("Number measures", () => { describe("dimension", () => { it("should create dimensions with value 1", () => { - expect(Measure.dimension("foo", "f")).toEqual({ value: 1, unit: { foo: ["f", 1] }, symbol: "f" }); + expect(Measure.dimension("foo", "f")).toEqual({ value: 1, unit: { foo: ["f", "1"] }, symbol: "f" }); }); }); @@ -96,7 +96,7 @@ describe("Number measures", () => { }); it("pow", () => { - expect(Measure.pow(Measure.of(3, meters), 4)).toEqual(Measure.of(81, meters.toThe(4))); + expect(Measure.pow(Measure.of(3, meters), "4")).toEqual(Measure.of(81, meters.toThe("4"))); }); it("round", () => { @@ -160,8 +160,8 @@ describe("Number measures", () => { expect(value.inverse()).toEqual(Measure.of(0.1, meters.inverse())); expect(value.reciprocal()).toEqual(Measure.of(0.1, meters.inverse())); - expect(value.toThe(0)).toEqual(Measure.dimensionless(1)); - expect(value.toThe(1)).toEqual(Measure.of(10, meters)); + expect(value.toThe("0")).toEqual(Measure.dimensionless(1)); + expect(value.toThe("1")).toEqual(Measure.of(10, meters)); expect(value.squared()).toEqual(Measure.of(100, meters.squared())); expect(value.cubed()).toEqual(Measure.of(1000, meters.cubed())); }); @@ -255,8 +255,8 @@ describe("Number measures", () => { it("should format units with only negative exponents", () => { expectFormat(seconds.inverse(), "1 s^-1"); - expectFormat(seconds.toThe(-2), "1 s^-2"); - expectFormat(seconds.toThe(-2).times(meters.toThe(-3)), "1 m^-3 * s^-2"); + expectFormat(seconds.toThe("-2"), "1 s^-2"); + expectFormat(seconds.toThe("-2").times(meters.toThe("-3")), "1 m^-3 * s^-2"); }); it("should format units with positive exponents and one negative exponent", () => { diff --git a/src/measure/__test__/unitValueArithmeticTests.ts b/src/measure/__test__/unitValueArithmeticTests.ts index 4f2a10e..869c776 100644 --- a/src/measure/__test__/unitValueArithmeticTests.ts +++ b/src/measure/__test__/unitValueArithmeticTests.ts @@ -17,88 +17,90 @@ describe("Unit value arithmetic", () => { describe("bases", () => { it("should construct base units", () => { - expect(dimension("x")).toEqual(addSymbols({ x: 1 })); + expect(dimension("x")).toEqual(addSymbols({ x: "1" })); }); }); describe("multiplication", () => { it("should multiply two different base units correctly", () => { - expect(multiplyUnits(x, y)).toEqual(addSymbols({ x: 1, y: 1 })); + expect(multiplyUnits(x, y)).toEqual(addSymbols({ x: "1", y: "1" })); }); it("should multiply two of the same base unit correctly", () => { - expect(multiplyUnits(x, x)).toEqual(addSymbols({ x: 2 })); + expect(multiplyUnits(x, x)).toEqual(addSymbols({ x: "2" })); }); it("should multiply complex units correctly", () => { - const left = addSymbols({ x: 1, y: -2 }); - const right = addSymbols({ y: 1, z: 2 }); - expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: 1, y: -1, z: 2 })); + const left = addSymbols({ x: "1", y: "-2" }); + const right = addSymbols({ y: "1", z: "2" }); + expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: "1", y: "-1", z: "2" })); }); it("should remove zero exponents from the result", () => { - const left = addSymbols({ x: 1, y: 2, z: 3 }); - const right = addSymbols({ x: -1, y: -2, z: -3 }); + const left = addSymbols({ x: "1", y: "2", z: "3" }); + const right = addSymbols({ x: "-1", y: "-2", z: "-3" }); expect(multiplyUnits(left, right)).toEqual({}); }); it("should handle explicitly undefined and 0 exponents", () => { - const left = addSymbols({ w: 0, x: 2, y: undefined }); - const right = addSymbols({ x: undefined, y: 0, z: undefined }); - expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: 2 })); + const left = addSymbols({ w: "0", x: "2", y: undefined }); + const right = addSymbols({ x: undefined, y: "0", z: undefined }); + expect(multiplyUnits(left, right)).toEqual(addSymbols({ x: "2" })); }); }); describe("division", () => { it("should correctly divide units", () => { - const left = addSymbols({ x: 2, y: 2 }); - const right = addSymbols({ x: 2, y: -1, z: 2 }); - expect(divideUnits(left, right)).toEqual(addSymbols({ y: 3, z: -2 })); + const left = addSymbols({ x: "2", y: "2" }); + const right = addSymbols({ x: "2", y: "-1", z: "2" }); + expect(divideUnits(left, right)).toEqual(addSymbols({ y: "3", z: "-2" })); }); }); describe("exponentiation", () => { it("should square a simple unit", () => { - expect(exponentiateUnit(x, 2)).toEqual(addSymbols({ x: 2 })); + expect(exponentiateUnit(x, "2")).toEqual(addSymbols({ x: "2" })); }); it("should cube a simple unit", () => { - expect(exponentiateUnit(x, 3)).toEqual(addSymbols({ x: 3 })); + expect(exponentiateUnit(x, "3")).toEqual(addSymbols({ x: "3" })); }); it("should square a complex unit", () => { - expect(exponentiateUnit(addSymbols({ x: 1, y: -2 }), 2)).toEqual(addSymbols({ x: 2, y: -4 })); + expect(exponentiateUnit(addSymbols({ x: "1", y: "-2" }), "2")).toEqual(addSymbols({ x: "2", y: "-4" })); }); it("should invert a unit", () => { - expect(exponentiateUnit(addSymbols({ x: -1, y: 2, z: -3 }), -1)).toEqual(addSymbols({ x: 1, y: -2, z: 3 })); + expect(exponentiateUnit(addSymbols({ x: "-1", y: "2", z: "-3" }), "-1")).toEqual( + addSymbols({ x: "1", y: "-2", z: "3" }), + ); }); it("should return the same unit when raised to the one", () => { - const input = addSymbols({ x: -1, y: 2, z: -3 }); - expect(exponentiateUnit(input, 1)).toEqual(input); + const input = addSymbols({ x: "-1", y: "2", z: "-3" }); + expect(exponentiateUnit(input, "1")).toEqual(input); }); it("should return a dimensionless unit when raised to the zero", () => { - expect(exponentiateUnit(addSymbols({ x: -1, y: 2, z: -3 }), 0)).toEqual({}); + expect(exponentiateUnit(addSymbols({ x: "-1", y: "2", z: "-3" }), "0")).toEqual({}); }); it("should handle explicitly undefined and 0 exponents", () => { - expect(exponentiateUnit(addSymbols({ x: 2, y: undefined, z: 0 }), 2)).toEqual(addSymbols({ x: 4 })); + expect(exponentiateUnit(addSymbols({ x: "2", y: undefined, z: "0" }), "2")).toEqual(addSymbols({ x: "4" })); }); }); describe("roots", () => { it("should square root the unit", () => { - expect(nthRootUnit(addSymbols({ x: 4, y: -2 }), 2)).toEqual(addSymbols({ x: 2, y: -1 })); + expect(nthRootUnit(addSymbols({ x: "4", y: "-2" }), "2")).toEqual(addSymbols({ x: "2", y: "-1" })); }); it("should cube root the unit", () => { - expect(nthRootUnit(addSymbols({ x: 3, y: -3 }), 3)).toEqual(addSymbols({ x: 1, y: -1 })); + expect(nthRootUnit(addSymbols({ x: "3", y: "-3" }), "3")).toEqual(addSymbols({ x: "1", y: "-1" })); }); it("should handle explicitly undefined and 0 exponents", () => { - expect(nthRootUnit(addSymbols({ x: 2, y: undefined, z: 0 }), 2)).toEqual(addSymbols({ x: 1 })); + expect(nthRootUnit(addSymbols({ x: "2", y: undefined, z: "0" }), "2")).toEqual(addSymbols({ x: "1" })); }); }); }); diff --git a/src/measure/format.ts b/src/measure/format.ts index 9ed34d3..667da6f 100644 --- a/src/measure/format.ts +++ b/src/measure/format.ts @@ -1,4 +1,4 @@ -import { Exponent } from "../exponent"; +import { getExponentValue, negateExponent } from "../exponent/exponentValueArithmetic"; import { SymbolAndExponent, UnitWithSymbols } from "./unitTypeArithmetic"; export function formatUnit(unit: UnitWithSymbols): string { @@ -11,8 +11,8 @@ export function formatUnit(unit: UnitWithSymbols): string { return ""; } - const positive = dimensions.filter(([_, dim]) => dim > 0); - const negative = dimensions.filter(([_, dim]) => dim < 0); + const positive = dimensions.filter(([_, dim]) => getExponentValue(dim) > 0); + const negative = dimensions.filter(([_, dim]) => getExponentValue(dim) < 0); if (positive.length === 0) { return formatDimensions(negative); @@ -28,7 +28,7 @@ export function formatUnit(unit: UnitWithSymbols): string { } function isDimensionPresent(dimension: SymbolAndExponent | undefined): dimension is SymbolAndExponent { - return dimension !== undefined && dimension[1] !== 0; + return dimension !== undefined && dimension[1] !== "0"; } function orderDimensions([leftSymbol]: SymbolAndExponent, [rightSymbol]: SymbolAndExponent): number { @@ -38,14 +38,14 @@ function orderDimensions([leftSymbol]: SymbolAndExponent, [rightSymbol]: SymbolA function formatDimensions(dimensions: SymbolAndExponent[]): string { return dimensions .map(([symbol, exponent]) => { - const exponentStr = exponent !== 1 ? `^${exponent}` : ""; + const exponentStr = exponent !== "1" ? `^${exponent}` : ""; return `${symbol}${exponentStr}`; }) .join(" * "); } function negateDimension([symbol, exponent]: SymbolAndExponent): SymbolAndExponent { - return [symbol, -exponent as Exponent]; + return [symbol, negateExponent(exponent)]; } function maybeParenthesize(text: string, parenthesize: boolean): string { diff --git a/src/measure/genericMeasure.ts b/src/measure/genericMeasure.ts index 29f9767..e77a7dc 100644 --- a/src/measure/genericMeasure.ts +++ b/src/measure/genericMeasure.ts @@ -1,4 +1,3 @@ -import { Exponent } from "../exponent"; import { AllowedExponents, DivideUnits, @@ -25,7 +24,7 @@ export interface INumericOperations { /** Returns the quotient of two numbers of type N */ div(left: N, right: N): N; /** Returns the base raised to the exponent for numbers of type N */ - pow(base: N, exponent: Exponent): N; + pow(base: N, exponent: number): N; /** Compares two numbers returning a negative, zero, or positive value. */ compare(left: N, right: N): number; /** Formats a number for display */ @@ -46,14 +45,14 @@ export interface IGenericMeasure { * this function will have type `never`. * @returns this measure multiplied by itself */ - squared: 2 extends AllowedExponents ? () => IGenericMeasure> : never; + squared: "2" extends AllowedExponents ? () => IGenericMeasure> : never; /** * If this measure can be cubed, cubes it. If this measure cannot be cubed (due to exponent limitations), then * this function will have type `never`. * @returns this cube of this measure */ - cubed: 3 extends AllowedExponents ? () => IGenericMeasure> : never; + cubed: "3" extends AllowedExponents ? () => IGenericMeasure> : never; /** * Raises this measure to a given power. If the result would give exponents outside of the allowable bounds, this @@ -122,13 +121,13 @@ export interface IGenericMeasure { * Returns the reciprocal of this measure. * @returns the reciprocal of this measure with a recriprocal unit */ - inverse(): IGenericMeasure>; + inverse(): IGenericMeasure>; /** * Returns the reciprocal of this measure. * @returns the reciprocal of this measure with a recriprocal unit */ - reciprocal(): IGenericMeasure>; + reciprocal(): IGenericMeasure>; /** * Maps the value and possibly unit of this measure. diff --git a/src/measure/genericMeasureClass.ts b/src/measure/genericMeasureClass.ts index 8c6a805..180c986 100644 --- a/src/measure/genericMeasureClass.ts +++ b/src/measure/genericMeasureClass.ts @@ -1,3 +1,4 @@ +import { getExponentValue } from "../exponent/exponentValueArithmetic"; import { formatUnit } from "./format"; import { IGenericMeasure, INumericOperations } from "./genericMeasure"; import { @@ -21,8 +22,8 @@ type GenericMeasureConstructor = new ( export function createMeasureClass(num: INumericOperations): GenericMeasureConstructor { class Measure implements IGenericMeasure { public readonly symbol: string | undefined; - public squared!: 2 extends AllowedExponents ? () => IGenericMeasure> : never; - public cubed!: 3 extends AllowedExponents ? () => IGenericMeasure> : never; + public squared!: "2" extends AllowedExponents ? () => IGenericMeasure> : never; + public cubed!: "3" extends AllowedExponents ? () => IGenericMeasure> : never; constructor(public readonly value: N, public readonly unit: UnitWithSymbols, symbol?: string) { this.symbol = symbol; @@ -49,11 +50,13 @@ export function createMeasureClass(num: INumericOperations): GenericMeasur public times>( other: IGenericMeasure, ): IGenericMeasure> { - return new Measure(num.mult(this.value, other.value), multiplyUnits(this.unit, other.unit)); + // HACKHACK Need to cast as any to get around excessively deep type instantiation error + return new Measure(num.mult(this.value, other.value), multiplyUnits(this.unit, other.unit)) as any; } public over>(other: IGenericMeasure): IGenericMeasure> { - return new Measure(num.div(this.value, other.value), divideUnits(this.unit, other.unit)); + // HACKHACK Need to cast as any to get around excessively deep type instantiation error + return new Measure(num.div(this.value, other.value), divideUnits(this.unit, other.unit)) as any; } public per>(other: IGenericMeasure): IGenericMeasure> { @@ -65,23 +68,23 @@ export function createMeasureClass(num: INumericOperations): GenericMeasur } public toThe>(power: E): IGenericMeasure> { - return new Measure(num.pow(this.value, power), exponentiateUnit(this.unit, power)) as any; + return new Measure(num.pow(this.value, getExponentValue(power)), exponentiateUnit(this.unit, power)); } - public inverse(): IGenericMeasure> { - return this.toThe(-1); + public inverse(): IGenericMeasure> { + return this.toThe("-1"); } - public reciprocal(): IGenericMeasure> { - return this.toThe(-1); + public reciprocal(): IGenericMeasure> { + return this.toThe("-1"); } public unsafeMap( valueMap: (value: N) => N, unitMap?: (unit: UnitWithSymbols) => UnitWithSymbols, - ): IGenericMeasure { + ): IGenericMeasure { const newUnit = unitMap === undefined ? this.unit : unitMap(this.unit); - return new Measure(valueMap(this.value), newUnit); + return new Measure(valueMap(this.value), (newUnit as unknown) as UnitWithSymbols); } // Comparisons @@ -138,11 +141,11 @@ export function createMeasureClass(num: INumericOperations): GenericMeasur } Measure.prototype.squared = function(): any { - return this.toThe(2); + return this.toThe("2"); }; Measure.prototype.cubed = function(): any { - return this.toThe(3); + return this.toThe("3"); }; return Measure; diff --git a/src/measure/genericMeasureFactory.ts b/src/measure/genericMeasureFactory.ts index 8ed601c..aaf8914 100644 --- a/src/measure/genericMeasureFactory.ts +++ b/src/measure/genericMeasureFactory.ts @@ -6,7 +6,7 @@ import { Unit } from "./unitTypeArithmetic"; import { dimension } from "./unitValueArithmetic"; type DimensionResult = true extends IsSingleStringLiteral - ? IGenericMeasure + ? IGenericMeasure : never; /** The functions needed to construct a measure of a given numeric type */ diff --git a/src/measure/genericMeasureUtils.ts b/src/measure/genericMeasureUtils.ts index 9cd11cd..d026947 100644 --- a/src/measure/genericMeasureUtils.ts +++ b/src/measure/genericMeasureUtils.ts @@ -1,4 +1,4 @@ -import { NonZeroExponent } from "../exponent/exponentArithmetic"; +import { NonZeroExponent } from "../exponent"; import { IGenericMeasure } from "./genericMeasure"; import { NthRootUnit, RadicandUnit, Unit } from "./unitTypeArithmetic"; import { nthRootUnit } from "./unitValueArithmetic"; diff --git a/src/measure/numberMeasure.ts b/src/measure/numberMeasure.ts index d470c5a..0c69d38 100644 --- a/src/measure/numberMeasure.ts +++ b/src/measure/numberMeasure.ts @@ -11,8 +11,8 @@ interface IMeasureStaticMethods { round: UnaryFn; trunc: UnaryFn; hypot: SpreadFn; - sqrt: NthRootFn<2>; - cbrt: NthRootFn<3>; + sqrt: NthRootFn<"2">; + cbrt: NthRootFn<"3">; } const staticMethods: IMeasureStaticMethods = { @@ -23,8 +23,8 @@ const staticMethods: IMeasureStaticMethods = { round: wrapUnaryFn(Math.round), trunc: wrapUnaryFn(Math.trunc), hypot: wrapSpreadFn(Math.hypot), - sqrt: wrapRootFn(Math.sqrt, 2), - cbrt: wrapRootFn(Math.cbrt, 3), + sqrt: wrapRootFn(Math.sqrt, "2"), + cbrt: wrapRootFn(Math.cbrt, "3"), }; const numericOps: INumericOperations = { diff --git a/src/measure/unitTypeArithmetic.ts b/src/measure/unitTypeArithmetic.ts index 9f39778..c90daac 100644 --- a/src/measure/unitTypeArithmetic.ts +++ b/src/measure/unitTypeArithmetic.ts @@ -21,7 +21,7 @@ export type SymbolAndExponent = [string, Exponent]; // Multiplication /** Returns the product of two units. This is the sum of two dimension vectors. */ -export type MultiplyUnits> = CleanUnit< +export type MultiplyUnits = CleanUnit< { [Dim in keyof L | keyof R]: AddExponents, GetExponent> } >; @@ -41,12 +41,12 @@ export type DivisorUnit = Partial<{ [D in keyof U]: SubtrahendOf // Exponentiation /** Returns the unit raised to a power. This is the scalar multiple of the dimension vector. */ -export type ExponentiateUnit = 0 extends N +export type ExponentiateUnit = "0" extends N ? {} : { [Dim in keyof U]: MultiplyExponents, N> }; /** Returns the union of exponents to which a given unit is allowed to be raised. */ -export type AllowedExponents = Exclude> | -1 | 0 | 1; +export type AllowedExponents = Exclude> | "-1" | "0" | "1"; /** Returns the union of exponents that raising and exponent to would produce an error. */ type NonAllowedExponents = { @@ -70,15 +70,15 @@ export type RadicandUnit = { /** Makes a unit pretty in intellisense views. */ // `ExponentiateUnit` is a noop that seems to accomplish this but is slow to compile and we should see if there's // a workaround. -type CleanUnit = ExponentiateUnit, 1>; +type CleanUnit = ExponentiateUnit, "1">; /** Removes all zero exponent dimensions from a dimension vector */ type StripZeroes = { [Dim in NonZeroKeys]: U[Dim] }; /** Gets the union of all dimensions of a unit with non zero or null exponents */ -type NonZeroKeys = { [Dim in keyof U]: NonNullable extends 0 ? never : Dim }[keyof U]; +type NonZeroKeys = { [Dim in keyof U]: NonNullable extends "0" ? never : Dim }[keyof U]; /** Get the exponent at a given dimension of a unit, or 0 if that dimension is undefined */ -type GetExponent = D extends keyof U ? NonNullable : 0; +type GetExponent = D extends keyof U ? NonNullable : "0"; -type CleanExponent = undefined extends E ? 0 : NonNullable; +type CleanExponent = undefined extends E ? "0" : NonNullable; diff --git a/src/measure/unitValueArithmetic.ts b/src/measure/unitValueArithmetic.ts index 26503f2..5a2fc73 100644 --- a/src/measure/unitValueArithmetic.ts +++ b/src/measure/unitValueArithmetic.ts @@ -1,4 +1,5 @@ import { Exponent, NonZeroExponent } from "../exponent"; +import { addExponents, divideExponents, multiplyExponents } from "../exponent/exponentValueArithmetic"; import { AllowedExponents, DivideUnits, @@ -13,8 +14,8 @@ import { UnitWithSymbols, } from "./unitTypeArithmetic"; -export function dimension(dim: Dim, symbol?: string): UnitWithSymbols<{ [D in Dim]: 1 }> { - return { [dim]: [symbol || dim, 1] } as any; +export function dimension(dim: Dim, symbol?: string): UnitWithSymbols<{ [D in Dim]: "1" }> { + return { [dim]: [symbol || dim, "1"] } as any; } export function multiplyUnits>( @@ -24,7 +25,7 @@ export function multiplyUnits>( const result: UnitWithSymbols = {}; for (const dimension in left) { const symbolAndExponent = copySymbolAndExponent(left, dimension); - if (symbolAndExponent !== undefined && symbolAndExponent[1] !== 0) { + if (symbolAndExponent !== undefined && symbolAndExponent[1] !== "0") { result[dimension] = symbolAndExponent; } } @@ -36,13 +37,13 @@ export function multiplyUnits>( const [, exponent] = symbolAndExponent; const resultValue: SymbolAndExponent | undefined = result[dimension]; if (resultValue !== undefined) { - const newExponent = (resultValue[1] + exponent) as Exponent; - if (newExponent === 0) { + const newExponent = addExponents(resultValue[1], exponent); + if (newExponent === "0") { delete result[dimension]; } else { resultValue[1] = newExponent; } - } else if (exponent !== 0) { + } else if (exponent !== "0") { result[dimension] = symbolAndExponent; } } @@ -62,24 +63,25 @@ export function divideUnits>( left: UnitWithSymbols, right: UnitWithSymbols, ): UnitWithSymbols> { - return multiplyUnits(left, exponentiateUnit(right, -1)) as any; + const rightInverse = exponentiateUnit(right, "-1") as any; + return multiplyUnits(left, rightInverse) as any; } export function exponentiateUnit>( unit: UnitWithSymbols, power: N, ): UnitWithSymbols> { - return expAndRootImpl(unit, exponent => exponent * power); + return expAndRootImpl(unit, exponent => multiplyExponents(exponent, power)); } export function nthRootUnit, N extends NonZeroExponent>( unit: UnitWithSymbols, root: N, ): UnitWithSymbols> { - return expAndRootImpl(unit, exponent => exponent / root); + return expAndRootImpl(unit, exponent => divideExponents(exponent, root)); } -function expAndRootImpl(unit: UnitWithSymbols, updateExponent: (exp: Exponent) => number): any { +function expAndRootImpl(unit: UnitWithSymbols, updateExponent: (exp: Exponent) => Exponent): any { const result: UnitWithSymbols = {}; for (const dimension in unit) { const symbolAndExponent = unit[dimension]; @@ -87,8 +89,8 @@ function expAndRootImpl(unit: UnitWithSymbols, updateExponent: (exp: Exponent) = continue; } const [symbol, exponent] = symbolAndExponent; - const newExponent = updateExponent(exponent) as Exponent; - if (newExponent !== 0) { + const newExponent = updateExponent(exponent); + if (newExponent !== "0") { result[dimension] = [symbol, newExponent]; } } diff --git a/src/unit/base.ts b/src/unit/base.ts index 3a4ecd3..ba6d332 100644 --- a/src/unit/base.ts +++ b/src/unit/base.ts @@ -15,7 +15,7 @@ interface IBaseUnitsMap { bits: "memory"; } -export type BaseUnits = { [U in keyof IBaseUnitsMap]: IGenericMeasure }; +export type BaseUnits = { [U in keyof IBaseUnitsMap]: IGenericMeasure }; export const createBaseUnits = (MeasureType: GenericMeasureType): BaseUnits => ({ meters: MeasureType.dimension("length", "m"), diff --git a/src/unit/quantities.ts b/src/unit/quantities.ts index b43e7e5..2b2bc2a 100644 --- a/src/unit/quantities.ts +++ b/src/unit/quantities.ts @@ -60,11 +60,11 @@ const Frequency = Time.inverse(); /** 1 / s² */ export type FrequencyDrift = LiftMeasure; -const FrequencyDrift = Time.toThe(-2); +const FrequencyDrift = Time.toThe("-2"); /** 1 / m² */ export type FuelEfficiency = LiftMeasure; -const FuelEfficiency = Length.toThe(-2); +const FuelEfficiency = Length.toThe("-2"); /** 1 / m */ export type Wavenumber = LiftMeasure; diff --git a/test/types/exponents.ts b/test/types/exponents.ts index 4b59610..a4dcb34 100644 --- a/test/types/exponents.ts +++ b/test/types/exponents.ts @@ -1,14 +1,14 @@ import { AddendOf, MultiplicandOf, Exponent, ProductOf, SubtrahendOf } from "../../src/exponent"; import { IsSame } from "./utils"; -type AddendOf3 = IsSame<-5 | -4 | -3 | -2 | -1 | 0 | 1 | 2, AddendOf<3>>; // $ExpectType true -type AddendOf0 = AddendOf<0>; // $ExpectType Exponent +type AddendOf3 = IsSame<"-5" | "-4" | "-3" | "-2" | "-1" | "0" | "1" | "2", AddendOf<"3">>; // $ExpectType true +type AddendOf0 = AddendOf<"0">; // $ExpectType Exponent -type SubtrahendOf3 = IsSame<-2 | -1 | 0 | 1 | 2 | 3 | 4 | 5, SubtrahendOf<3>>; // $ExpectType true -type SubtrahendOf0 = SubtrahendOf<0>; // $ExpectType Exponent +type SubtrahendOf3 = IsSame<"-2" | "-1" | "0" | "1" | "2" | "3" | "4" | "5", SubtrahendOf<"3">>; // $ExpectType true +type SubtrahendOf0 = SubtrahendOf<"0">; // $ExpectType Exponent -type MultiplicandOf2 = IsSame<-2 | -1 | 0 | 1 | 2, MultiplicandOf<2>>; // $ExpectType true -type MultiplicandOf3 = IsSame<-1 | 0 | 1, MultiplicandOf<3>>; // $ExpectType true -type MultiplicandOf0 = MultiplicandOf<0>; // $ExpectType Exponent +type MultiplicandOf2 = IsSame<"-2" | "-1" | "0" | "1" | "2", MultiplicandOf<"2">>; // $ExpectType true +type MultiplicandOf3 = IsSame<"-1" | "0" | "1", MultiplicandOf<"3">>; // $ExpectType true +type MultiplicandOf0 = MultiplicandOf<"0">; // $ExpectType Exponent -type ProductOf2 = IsSame<-4 | -2 | 0 | 2 | 4, ProductOf<2>>; // $ExpectType true +type ProductOf2 = IsSame<"-4" | "-2" | "0" | "2" | "4", ProductOf<"2">>; // $ExpectType true diff --git a/test/types/measures.ts b/test/types/measures.ts index eac3977..855ed63 100644 --- a/test/types/measures.ts +++ b/test/types/measures.ts @@ -2,30 +2,30 @@ import { Acceleration, Area, kilograms, Measure, meters, newtons, seconds, Volum // Valid usages -Measure.dimension("x"); // $ExpectType IGenericMeasure +Measure.dimension("x"); // $ExpectType IGenericMeasure -const m = meters; // $ExpectType IGenericMeasure -const s = seconds; // $ExpectType IGenericMeasure -const n = newtons; // $ExpectType IGenericMeasure +const m = meters; // $ExpectType IGenericMeasure +const s = seconds; // $ExpectType IGenericMeasure +const n = newtons; // $ExpectType IGenericMeasure -const a = newtons.over(kilograms); // $ExpectType IGenericMeasure -const accel: Acceleration = a; // $ExpectType IGenericMeasure +const a = newtons.over(kilograms); // $ExpectType IGenericMeasure +const accel: Acceleration = a; // $ExpectType IGenericMeasure -const absement = meters.times(seconds); // $ExpectType IGenericMeasure -const velocity = meters.over(seconds); // $ExpectType IGenericMeasure +const absement = meters.times(seconds); // $ExpectType IGenericMeasure +const velocity = meters.over(seconds); // $ExpectType IGenericMeasure -meters.plus(meters); // $ExpectType IGenericMeasure -meters.minus(meters); // $ExpectType IGenericMeasure -meters.negate(); // $ExpectType IGenericMeasure -meters.scale(2); // $ExpectType IGenericMeasure +meters.plus(meters); // $ExpectType IGenericMeasure +meters.minus(meters); // $ExpectType IGenericMeasure +meters.negate(); // $ExpectType IGenericMeasure +meters.scale(2); // $ExpectType IGenericMeasure -velocity.squared(); // $ExpectType IGenericMeasure -absement.cubed(); // $ExpectType IGenericMeasure -absement.inverse(); // $ExpectType IGenericMeasure -velocity.toThe(0); // $ExpectType IGenericMeasure +velocity.squared(); // $ExpectType IGenericMeasure +absement.cubed(); // $ExpectType IGenericMeasure +absement.inverse(); // $ExpectType IGenericMeasure +velocity.toThe("0"); // $ExpectType IGenericMeasure -Measure.sqrt(velocity.toThe(-4)); // $ExpectType IGenericMeasure -Measure.cbrt(absement.toThe(3)); // $ExpectType IGenericMeasure +Measure.sqrt(velocity.toThe("-4")); // $ExpectType IGenericMeasure +Measure.cbrt(absement.toThe("3")); // $ExpectType IGenericMeasure // Error usages @@ -44,8 +44,8 @@ area.minus(volume); // $ExpectError const sq = volume.squared; // $ExpectType never const cu = area.cubed; // $ExpectType never -area.toThe(4); // $ExpectError -volume.toThe(-2); // $ExpectError +area.toThe("4"); // $ExpectError +volume.toThe("-2"); // $ExpectError Measure.sqrt(volume); // $ExpectError Measure.cbrt(area); // $ExpectError diff --git a/test/types/units.ts b/test/types/units.ts index 3f2c070..b4ac098 100644 --- a/test/types/units.ts +++ b/test/types/units.ts @@ -15,45 +15,45 @@ type Extends = A extends B ? true : false; // MultiplyUnits -type SelfMultiplication = MultiplyUnits<{ a: 2 }, { a: 2 }>; // $ExpectType { a: 4; } -type MultiplySeveralDimensions = MultiplyUnits<{ a: 2; b: -1 }, { a: -2; b: 2; c: 1 }>; // $ExpectType { b: 1; c: 1; } +type SelfMultiplication = MultiplyUnits<{ a: "2" }, { a: "2" }>; // $ExpectType { a: "4"; } +type MultiplySeveralDimensions = MultiplyUnits<{ a: "2"; b: "-1" }, { a: "-2"; b: "2"; c: "1" }>; // $ExpectType { b: "1"; c: "1"; } // MultiplicandUnit -type MultiplicandAllowsOtherDimsWithAnyExponent = Extends<{ b: Exponent }, MultiplicandUnit<{ a: 2 }>>; // $ExpectType true -type MultiplicandRejectsSameDimWithBadExponent = Extends<{ a: -4 }, MultiplicandUnit<{ a: -2 }>>; // $ExpectType false -type MultiplicandAcceptsSameDimWithGoodExponent = Extends<{ a: 2 }, MultiplicandUnit<{ a: 3 }>>; // $ExpectType true -type MultiplicandAllowsMultiDims = Extends<{ a: 3; c: -4; d: 5 }, MultiplicandUnit<{ a: 1; c: 2 }>>; // $ExpectType true -type MultiplicandRejectsMultiDims = Extends<{ a: 1; c: 0 }, MultiplicandUnit<{ a: 5; b: -5 }>>; // $ExpectType false +type MultiplicandAllowsOtherDimsWithAnyExponent = Extends<{ b: Exponent }, MultiplicandUnit<{ a: "2" }>>; // $ExpectType true +type MultiplicandRejectsSameDimWithBadExponent = Extends<{ a: "-4" }, MultiplicandUnit<{ a: "-2" }>>; // $ExpectType false +type MultiplicandAcceptsSameDimWithGoodExponent = Extends<{ a: "2" }, MultiplicandUnit<{ a: "3" }>>; // $ExpectType true +type MultiplicandAllowsMultiDims = Extends<{ a: "3"; c: "-4"; d: "5" }, MultiplicandUnit<{ a: "1"; c: "2" }>>; // $ExpectType true +type MultiplicandRejectsMultiDims = Extends<{ a: "1"; c: "0" }, MultiplicandUnit<{ a: "5"; b: "-5" }>>; // $ExpectType false // DivideUnits -type Division = DivideUnits<{ a: 2; b: -1 }, { a: 2; b: -2; c: -1 }>; // $ExpectType { b: 1; c: 1; } +type Division = DivideUnits<{ a: "2"; b: "-1" }, { a: "2"; b: "-2"; c: "-1" }>; // $ExpectType { b: "1"; c: "1"; } // ExponentiateUnit -type RaisingToTheZero = ExponentiateUnit<{ a: 2; b: -1 }, 0>; // $ExpectType {} -type RaisingToTheOne = ExponentiateUnit<{ a: 2; b: 3 }, 1>; // $ExpectType { a: 2; b: 3; } -type Squaring = ExponentiateUnit<{ a: 2; b: -1 }, 2>; // $ExpectType { a: 4; b: -2; } -type Cubing = ExponentiateUnit<{ a: 1 }, 3>; // $ExpectType { a: 3; } +type RaisingToTheZero = ExponentiateUnit<{ a: "2"; b: "-1" }, "0">; // $ExpectType {} +type RaisingToTheOne = ExponentiateUnit<{ a: "2"; b: "3" }, "1">; // $ExpectType { a: "2"; b: "3"; } +type Squaring = ExponentiateUnit<{ a: "2"; b: "-1" }, "2">; // $ExpectType { a: "4"; b: "-2"; } +type Cubing = ExponentiateUnit<{ a: "1" }, "3">; // $ExpectType { a: "3"; } // AllowedExponents -type AllowedLargeExponents = AllowedExponents<{ a: 1; b: 0 }>; // $ExpectType Exponent -type AllowedMediumExponents = IsSame<-2 | -1 | 0 | 1 | 2, AllowedExponents<{ a: 2; b: 1 }>>; // $ExpectType true -type AllowedSmallExponents = IsSame<-1 | 0 | 1, AllowedExponents<{ a: 3; b: 1 }>>; // $ExpectType true +type AllowedLargeExponents = AllowedExponents<{ a: "1"; b: "0" }>; // $ExpectType Exponent +type AllowedMediumExponents = IsSame<"-2" | "-1" | "0" | "1" | "2", AllowedExponents<{ a: "2"; b: "1" }>>; // $ExpectType true +type AllowedSmallExponents = IsSame<"-1" | "0" | "1", AllowedExponents<{ a: "3"; b: "1" }>>; // $ExpectType true // NthRootUnit -type SquareRooting = NthRootUnit<{ a: 4; b: -2 }, 2>; // $ExpectType { a: 2; b: -1; } -type CubeRooting = NthRootUnit<{ a: 3; b: -3 }, 3>; // $ExpectType { a: 1; b: -1; } +type SquareRooting = NthRootUnit<{ a: "4"; b: "-2" }, "2">; // $ExpectType { a: "2"; b: "-1"; } +type CubeRooting = NthRootUnit<{ a: "3"; b: "-3" }, "3">; // $ExpectType { a: "1"; b: "-1"; } // RadicandUnit -type RadicandAcceptsPerfectSquares = Extends<{ a: 2; b: -4; c: 0 }, RadicandUnit<2>>; // $ExpectType true -type RadicandRejectsNonPerfectSquares = Extends<{ a: 2; b: 1 }, RadicandUnit<2>>; // $ExpectType false -type RadicandAcceptsPerfectCubes = Extends<{ a: 3; b: -3 }, RadicandUnit<3>>; // $ExpectType true -type RadicandRejectsNonPerfectCubes = Extends<{ a: 3; b: 2 }, RadicandUnit<3>>; // $ExpectType false +type RadicandAcceptsPerfectSquares = Extends<{ a: "2"; b: "-4"; c: "0" }, RadicandUnit<"2">>; // $ExpectType true +type RadicandRejectsNonPerfectSquares = Extends<{ a: "2"; b: "1" }, RadicandUnit<"2">>; // $ExpectType false +type RadicandAcceptsPerfectCubes = Extends<{ a: "3"; b: "-3" }, RadicandUnit<"3">>; // $ExpectType true +type RadicandRejectsNonPerfectCubes = Extends<{ a: "3"; b: "2" }, RadicandUnit<"3">>; // $ExpectType false // IsSingleStringLiteral diff --git a/yarn.lock b/yarn.lock index d51b100..ce98d8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + "@babel/code-frame@^7.0.0-beta.35": version "7.0.0-beta.46" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.46.tgz#e0d002100805daab1461c0fcb32a07e304f3a4f4" @@ -16,6 +23,15 @@ esutils "^2.0.2" js-tokens "^3.0.0" +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + "@types/commonmark@^0.27.1": version "0.27.1" resolved "https://registry.yarnpkg.com/@types/commonmark/-/commonmark-0.27.1.tgz#d40637518c557ceaa5006149b6a0a044a3dffe29" @@ -2029,11 +2045,19 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -"js-tokens@^3.0.0 || ^4.0.0": +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.7.0: version "3.11.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" @@ -3549,22 +3573,24 @@ tslint-plugin-prettier@^1.3.0: eslint-plugin-prettier "^2.2.0" tslib "^1.7.1" -tslint@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.10.0.tgz#11e26bccb88afa02dd0d9956cae3d4540b5f54c3" +tslint@^5.18.0: + version "5.18.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.18.0.tgz#f61a6ddcf372344ac5e41708095bbf043a147ac6" + integrity sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w== dependencies: - babel-code-frame "^6.22.0" + "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" chalk "^2.3.0" commander "^2.12.1" diff "^3.2.0" glob "^7.1.1" - js-yaml "^3.7.0" + js-yaml "^3.13.1" minimatch "^3.0.4" + mkdirp "^0.5.1" resolve "^1.3.2" semver "^5.3.0" tslib "^1.8.0" - tsutils "^2.12.1" + tsutils "^2.29.0" tslint@^5.9.1: version "5.11.0" @@ -3588,13 +3614,7 @@ tsutils@^1.4.0: version "1.9.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" -tsutils@^2.12.1: - version "2.26.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.26.2.tgz#a9f9f63434a456a5e0c95a45d9a59181cb32d3bf" - dependencies: - tslib "^1.8.1" - -tsutils@^2.27.2: +tsutils@^2.27.2, tsutils@^2.29.0: version "2.29.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== @@ -3622,10 +3642,10 @@ typescript@next: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.0-dev.20181116.tgz#0a405b3385d6f180df78bf557167c0a608b5a501" integrity sha512-+UUHAGhfccJe1ZnHbqp0uixDjT/yMguznOvF1p+16ytQPJaFo2MfIt6on4PZlgl7VhdxtENSfzl44xxjWsfj8Q== -typescript@~2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" - integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== +typescript@~3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.2.tgz#a09e1dc69bc9551cadf17dba10ee42cf55e5d56c" + integrity sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA== typestyle@^2.0.1: version "2.0.1"