Skip to content

Commit 0abbd22

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). This is a breaking change for anyone wrapping `Measure`.
1 parent 1b4d055 commit 0abbd22

12 files changed

+40
-28
lines changed

docs/examples/genericMeasureWrapped.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { WrappedNumber, wrap } from "./genericMeasureIntro";
22

33
// START
4-
import { createMeasureType, GenericMeasure, Unit } from "safe-units";
4+
import { type BasisType, createMeasureType, GenericMeasure, Unit } from "safe-units";
55

6-
type WrappedMeasure<B, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
6+
type WrappedMeasure<B extends BasisType, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
77
const WrappedMeasure = createMeasureType<WrappedNumber>({
88
one: () => wrap(1),
99
neg: x => wrap(-x.value),

docs/generic-measures.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ We can then use this class just as we would use `Measure`, except anywhere we'd
2323
Let's deconstruct this example to explain what's going on. First we start with this type definition:
2424

2525
```ts
26-
type WrappedMeasure<B, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
26+
type WrappedMeasure<B extends BasisType, U extends Unit<B>> = GenericMeasure<WrappedNumber, B, U>;
2727
```
2828

2929
This line isn't strictly necessary, but it is often useful to have our `WrappedMeasure` available as a type. Having a type for the measure is useful for writing generic functions on wrapped measures. All this line does is bind the numeric type of `GenericMeasure`. Similarly, the `Measure` type has the following definition:
3030

3131
```ts
32-
type Measure<B, U extends Unit<B>> = GenericMeasure<number, B, U>;
32+
type Measure<B extends BasisType, U extends Unit<B>> = GenericMeasure<number, B, U>;
3333
```
3434

3535
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:

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/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export {
1111
wrapUnaryFn,
1212
} from "./genericMeasureUtils";
1313
export { Measure } from "./numberMeasure";
14-
export { UnitSystem } from "./unitSystem";
14+
export { BasisType, UnitSystem } from "./unitSystem";
1515
export {
1616
CubeUnit,
1717
DimensionlessUnit,

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);

0 commit comments

Comments
 (0)