Skip to content

Commit b158d3d

Browse files
committed
Make Basis type parameters type-safe
Before, the type system would let you pass in anything as a basis, even things that weren't objects. Now, Basis types must conform to the type Record<string, string> (aliased as BasisType, with a doc comment). I had to change how the errorBasis test works (it's simpler now).
1 parent 1b4d055 commit b158d3d

File tree

9 files changed

+35
-23
lines changed

9 files changed

+35
-23
lines changed

src/measure/format.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { UnitSystem } from "./unitSystem";
1+
import { type BasisType, UnitSystem } from "./unitSystem";
22
import { Unit } from "./unitTypeArithmetic";
33

44
type SymbolAndExponent = [symbol: string, exponent: number];
55

6-
export function defaultFormatUnit<Basis>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>): string {
6+
export function defaultFormatUnit<Basis extends BasisType>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>): string {
77
const positive: SymbolAndExponent[] = [];
88
const negative: SymbolAndExponent[] = [];
99
unitSystem.getDimensions().forEach(dimension => {

src/measure/genericMeasure.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { UnitSystem } from "./unitSystem";
1+
import { type BasisType, UnitSystem } from "./unitSystem";
22
import { CubeUnit, DivideUnits, MultiplyUnits, ReciprocalUnit, SquareUnit, Unit } from "./unitTypeArithmetic";
33

44
export interface MeasureFormatter<N> {
55
formatValue?: (value: N) => string;
6-
formatUnit?: <Basis>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>) => string;
6+
formatUnit?: <Basis extends BasisType>(unit: Unit<Basis>, unitSystem: UnitSystem<Basis>) => string;
77
}
88

99
/** The set of numeric operations required to fully represent a `GenericMeasure` for a given numeric type */
@@ -29,7 +29,7 @@ export interface NumericOperations<N> {
2929
}
3030

3131
/** A numeric value with a corresponding unit of measurement. */
32-
export interface GenericMeasure<N, Basis, U extends Unit<Basis>> {
32+
export interface GenericMeasure<N, Basis extends BasisType, U extends Unit<Basis>> {
3333
/** The numeric value of this measure */
3434
readonly value: N;
3535
/** The unit of this measure */

src/measure/genericMeasureClass.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { defaultFormatUnit } from "./format";
22
import { GenericMeasure, MeasureFormatter, NumericOperations } from "./genericMeasure";
3-
import { UnitSystem } from "./unitSystem";
3+
import { type BasisType, UnitSystem } from "./unitSystem";
44
import { DivideUnits, MultiplyUnits, ReciprocalUnit, SquareUnit, Unit, CubeUnit } from "./unitTypeArithmetic";
55

66
interface GenericMeasureClass<N> {
7-
createMeasure: <Basis, U extends Unit<Basis>>(
7+
createMeasure: <Basis extends BasisType, U extends Unit<Basis>>(
88
value: N,
99
unit: U,
1010
unitSystem: UnitSystem<Basis>,
@@ -28,7 +28,7 @@ export function createMeasureClass<N>(num: NumericOperations<N>): GenericMeasure
2828
}
2929
}
3030

31-
class Measure<Basis, U extends Unit<Basis>> implements GenericMeasure<N, Basis, U> {
31+
class Measure<Basis extends BasisType, U extends Unit<Basis>> implements GenericMeasure<N, Basis, U> {
3232
constructor(
3333
public readonly value: N,
3434
public readonly unit: U,

src/measure/genericMeasureFactory.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GenericMeasure, NumericOperations } from "./genericMeasure";
22
import { createMeasureClass } from "./genericMeasureClass";
33
import { GenericMeasureStatic, getGenericMeasureStaticMethods } from "./genericMeasureStatic";
4-
import { UnitSystem } from "./unitSystem";
4+
import { type BasisType, UnitSystem } from "./unitSystem";
55
import { DimensionUnit, DimensionlessUnit, Unit } from "./unitTypeArithmetic";
66

77
/** The functions needed to construct a measure of a given numeric type */
@@ -16,7 +16,7 @@ interface GenericMeasureFactory<N> {
1616
* @param symbol the symbol of the base unit of the dimension (e.g. "m")
1717
* @returns A measure representing 1 base unit of the dimension (1 m)
1818
*/
19-
dimension<Basis, Dimension extends keyof Basis>(
19+
dimension<Basis extends BasisType, Dimension extends keyof Basis>(
2020
unitSystem: UnitSystem<Basis>,
2121
dimension: Dimension,
2222
symbol?: string,
@@ -28,7 +28,10 @@ interface GenericMeasureFactory<N> {
2828
* @param value the value of the measure
2929
* @returns a measure with no dimensions
3030
*/
31-
dimensionless<Basis>(unitSystem: UnitSystem<Basis>, value: N): GenericMeasure<N, Basis, DimensionlessUnit<Basis>>;
31+
dimensionless<Basis extends BasisType>(
32+
unitSystem: UnitSystem<Basis>,
33+
value: N,
34+
): GenericMeasure<N, Basis, DimensionlessUnit<Basis>>;
3235

3336
/**
3437
* Creates a measure as a multiple of another measure.
@@ -37,7 +40,7 @@ interface GenericMeasureFactory<N> {
3740
* @param symbol an optional unit symbol for this measure
3841
* @returns a measure of value number of quantities.
3942
*/
40-
of<Basis, U extends Unit<Basis>>(
43+
of<Basis extends BasisType, U extends Unit<Basis>>(
4144
value: N,
4245
quantity: GenericMeasure<N, Basis, U>,
4346
symbol?: string,

src/measure/genericMeasureStatic.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GenericMeasure, NumericOperations } from "./genericMeasure";
22
import { BinaryFn, PrefixFn, SpreadFn, wrapBinaryFn, wrapReducerFn } from "./genericMeasureUtils";
3+
import type { BasisType } from "./unitSystem";
34
import { DivideUnits, MultiplyUnits, Unit } from "./unitTypeArithmetic";
45

56
export interface GenericMeasureStatic<N> {
@@ -19,13 +20,13 @@ export interface GenericMeasureStatic<N> {
1920
subtract: BinaryFn<N>;
2021

2122
/** Static version of `left.times(right)` */
22-
multiply<Basis, Left extends Unit<Basis>, Right extends Unit<Basis>>(
23+
multiply<Basis extends BasisType, Left extends Unit<Basis>, Right extends Unit<Basis>>(
2324
left: GenericMeasure<N, Basis, Left>,
2425
right: GenericMeasure<N, Basis, Right>,
2526
): GenericMeasure<N, Basis, MultiplyUnits<Basis, Left, Right>>;
2627

2728
/** Static version of `left.div(right)` */
28-
divide<Basis, Left extends Unit<Basis>, Right extends Unit<Basis>>(
29+
divide<Basis extends BasisType, Left extends Unit<Basis>, Right extends Unit<Basis>>(
2930
left: GenericMeasure<N, Basis, Left>,
3031
right: GenericMeasure<N, Basis, Right>,
3132
): GenericMeasure<N, Basis, DivideUnits<Basis, Left, Right>>;

src/measure/genericMeasureUtils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import { GenericMeasure } from "./genericMeasure";
2+
import type { BasisType } from "./unitSystem";
23
import { Unit } from "./unitTypeArithmetic";
34

45
/** A function which applies a symbol prefix and multiplier to a given measure. */
5-
export type PrefixFn<N = number> = <Basis, U extends Unit<Basis>>(
6+
export type PrefixFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
67
measure: GenericMeasure<N, Basis, U>,
78
) => GenericMeasure<N, Basis, U>;
89

910
/** A function which transforms a single measure into another measure with the same unit. */
10-
export type UnaryFn<N = number> = <Basis, U extends Unit<Basis>>(
11+
export type UnaryFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
1112
x: GenericMeasure<N, Basis, U>,
1213
) => GenericMeasure<N, Basis, U>;
1314

1415
/** A function which transforms two measures with same unit into a single measure with the same unit. */
15-
export type BinaryFn<N = number> = <Basis, U extends Unit<any>>(
16+
export type BinaryFn<N = number> = <Basis extends BasisType, U extends Unit<any>>(
1617
left: GenericMeasure<N, Basis, U>,
1718
right: GenericMeasure<N, Basis, U>,
1819
) => GenericMeasure<N, Basis, U>;
1920

2021
/** A function which transforms one or more measure with the same unit into a single measure with the same unit. */
21-
export type SpreadFn<N = number> = <Basis, U extends Unit<Basis>>(
22+
export type SpreadFn<N = number> = <Basis extends BasisType, U extends Unit<Basis>>(
2223
first: GenericMeasure<N, Basis, U>,
2324
...rest: Array<GenericMeasure<N, Basis, U>>
2425
) => GenericMeasure<N, Basis, U>;

src/measure/numberMeasure.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GenericMeasure, NumericOperations } from "./genericMeasure";
22
import { createMeasureType, GenericMeasureType } from "./genericMeasureFactory";
33
import { SpreadFn, UnaryFn, wrapSpreadFn, wrapUnaryFn } from "./genericMeasureUtils";
4+
import type { BasisType } from "./unitSystem";
45
import { Unit } from "./unitTypeArithmetic";
56

67
interface MeasureStaticMethods {
@@ -35,5 +36,5 @@ const numericOps: NumericOperations<number> = {
3536
format: x => `${x}`,
3637
};
3738

38-
export type Measure<Basis, U extends Unit<Basis>> = GenericMeasure<number, Basis, U>;
39+
export type Measure<Basis extends BasisType, U extends Unit<Basis>> = GenericMeasure<number, Basis, U>;
3940
export const Measure: GenericMeasureType<number, MeasureStaticMethods> = createMeasureType(numericOps, staticMethods);

src/measure/unitSystem.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import {
77
Unit,
88
} from "./unitTypeArithmetic";
99

10-
type UnitSystemResult<Basis> = [Basis[keyof Basis]] extends [string]
10+
/**
11+
* The keys of a type system's basis are the names of the dimensions, and the values are the symbols
12+
* of each dimension's base unit.
13+
*/
14+
export type BasisType = Record<string, string>;
15+
16+
type UnitSystemResult<Basis extends BasisType> = [Basis[keyof Basis]] extends [string]
1117
? UnitSystem<Basis>
1218
: `Dimension '${NonStringValuedKeys<Basis>}' does not have a valid symbol`;
1319

@@ -20,7 +26,7 @@ type NonStringValuedKeys<T> = keyof {
2026
* are defined by the keys of the Basis type parameter. The base units are given by the symbol map passed into the
2127
* constructor.
2228
*/
23-
export class UnitSystem<Basis> implements UnitSystem<Basis> {
29+
export class UnitSystem<Basis extends BasisType> implements UnitSystem<Basis> {
2430
private readonly dimensions: Array<keyof Basis>;
2531

2632
/**
@@ -48,7 +54,7 @@ export class UnitSystem<Basis> implements UnitSystem<Basis> {
4854
* ...
4955
* });
5056
*/
51-
public static from<Basis>(symbols: Basis): UnitSystemResult<Basis> {
57+
public static from<Basis extends BasisType>(symbols: Basis): UnitSystemResult<Basis> {
5258
return new UnitSystem(symbols) as UnitSystemResult<Basis>;
5359
}
5460

test/measureTypeTests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,5 @@ const validUnitSystem = UnitSystem.from({ length: "m", mass: "kg", time: "s" } a
8484
expectTrue(value(validUnitSystem).hasType<UnitSystem<{ length: "m"; mass: "kg"; time: "s" }>>());
8585

8686
const errorBasis = { length: "m", mass: 3, time: "kg" };
87+
// @ts-expect-error errorBasis is not a valid basis for a type system
8788
const errorUnitSystem = UnitSystem.from(errorBasis);
88-
expectTrue(value(errorUnitSystem).hasType<"Dimension 'mass' does not have a valid symbol">());

0 commit comments

Comments
 (0)