GithubHelp home page GithubHelp logo

type-refinement-typescript's Introduction

Proposal: Optional type refinement for Typescript

Search Terms

type narrowing, type constraints, class / interface invariant, regular expression, typescript

Similar to microsoft/TypeScript#6579 "Suggestion: Regex-validated string type"
with the goal to be more general and less intrusive by addressing the concerns noted in that proposal.

Suggestion

The main goal of this proposal is to reflect constrains / invariants on primitives and objects via the type system.
To support this Typescript may introduce type refinement to express these constraints. The refined types support validation of literals at compile time and can also be used for type narrowing in control flow based type analysis.

Examples

Type refinement for a primitive type

Refine the representation of a date string to IsoDateTime validated by the refinement function isIsoDateTime() to enable compile time validation with a RegExp and control flow based type analysis.

type IsoDateTime = TypeRefinement<string, typeof isIsoDateTime>;

function isIsoDateTime(val: string): IsoDateTime | undefined {
    const regex = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
    return regex.test(val) ? (val as IsoDateTime) : undefined;
}

const isoDateOk: IsoDateTime = "2020-08-16T13:57:12.123Z"; // OK
const isoDateErr: IsoDateTime = "16/8/2020"; // error: type refinement 'IsoDateTime' violates 'isIsoDateTime()'

function testDateTime(val: string) {
    if (isIsoDateTime(val)) {
        val; //  refines to IsoDateTime if type refinement is enabled; Otherwise string
    } else {
        const dst: IsoDateTime = val; // is always string; with refinement - error: Type 'string' is not assignable to type 'IsoDateTime'.
    }
}

Example in Playground

Type refinement for an object type

Refine the representation of an interface to its invariant type Organization validated by the refinement function isValidOrganization() to enable compile time validation and control flow based type analysis.

interface IOrganization {
    employeeCount: number;
    externalCount: number;
}

type Organization = TypeRefinement<IOrganization, typeof isValidOrganization>;

function isValidOrganization(val: IOrganization): Organization | undefined {
    return val.externalCount < val.employeeCount ? val : undefined;
}

const organization: Organization = {
    employeeCount: 20,
    externalCount: 5
}; // OK (5 < 20)

Example in Playground

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of [TypeScript's Design Goals]

Goals

  • Compile time checks for

    • literals
    • refined types
  • Compile time type checks covers:

    • primitives: string, number and BigInt
    • composed types: object
  • Compile time checks of refined types for:

    • assignments
    • function calls - check signature
    • type definitions - unions & intersections
    • generics
  • Implement refinement validation by functions. E.g:

    • RegExp patterns enclosed by named functions

    • Common functions exposed by application or third party libraries with a signature like:
      (val: <base type>) => <refined type> | undefined

    • The name of a refinement function can be arbitrary. Is should be like: isMyType ()

  • Type refinement functions can also be used for runtime checks to avoid code duplication for compile time and runtime checks.
    In case of positive check the type gets refined.

  • Make type refinement checking optional per Typescript project. If disabled the compiler simply fall back to their base type T declared in: type
    type TypeRefinement<T, refinementFcn extends RefinementFcn<T>> = T;

Non-goals

  • emit no code based based on type declarations from type space

Use cases

  • Validate example literals used to illustrate usage of a complex type hierarchy (hundreds of classes) at compile time. This is especially very helpful for developers which are new to Typescript and/or new to a complex type hierarchy.

  • Create / validate / enhance unit tests especially for type declarations in DefinitelyTyped.

  • Represent & validate pattern and format annotated to properties in JSON Schema at compile time

  • Represent & validate the invariant of objects with inter-property dependencies at compile time

Compile time / malicious code

A requirement of this proposal is that user code need to be executed at compile time. So concerns about increased compile time and malicious validation code are valid.

As this feature is designed as Opt-In those concerns can be solved by simply not using it.

Precisionitis / Tower of babel

A valid concern about validation expressions is that authors of DefinitelyTyped may introduce validations to every type.
See: microsoft/TypeScript#6579 (comment)

As this proposal is not using meta data (via d.ts files) for validation only the 3rd party authors are able to introduce validation functions.
Authors of DefinitelyTyped have only the possibility to utilize these validation functions for refined types.

Compatibility

By using only two simple generics and returning the base type T in TypeRefinement this proposal preserves backward compatibility to early Typescript versions.

  • requires no change or extension of Typescript syntax
  • requires a specific handling by the compiler when assigning a refinement function to a refined type. More at docs of TypeRefinement

Structural typing

As Typescript is designed to be a structural typed language (instead of a nominal typed language) it may be useful to apply this to refined types. A refined typed may get (implicitly) an additional property 'refinementFcn' with the signature of the refinementFcn. So two individual type refinements are considered as equal if the have the same function signature.

As a result:

  • the union of two type refinements using the same base type is the union of both refined types
  • the intersection of two type refinements using the same base type results in: never

Naming

Alternative naming could by: type restriction, reduction or constraint

Implementation

This proposal requires adding a generic type TypeRefinement which is known thy the compiler to apply specific handling.

// --- lib.*.d.ts - predefined type refinement types

/**
 * A type refinement function is used as a validation function inside a TypeRefinement. Its signature is:
 *
 *     (val: <base type>) => <refined type> | undefined
 *
 * @result { msg: string }) - May be used to return a descriptive validation error
 */
type RefinementFcn<T> = (val: T, result?: { msg: string }) => T | undefined;

/**
 * TypeRefinement defines a refined type by creating a bi-directional relation of the refined type (MyType)
 * and its refinement function (isMyType). E.g. MyType <-> isMyType
 *
 *    function isMyType(val: string): MyType { return <MyType constraints fulfilled> ? val : undefined; }
 *    type MyType = TypeRefinement<string, typeof isMyType>;
 *
 * In case type refinement is enabled typeof inside a TypeRefinement create the described relation above.
 * When using refined types in code it enables:
 * - Execute refinementFcn to validate refined type for literals.
 * - Refine a type when calling the refinementFcn() with the given base type T.
 *
 * Current compilers without refinement support simply use the given base type T.
 */
type TypeRefinement<T, refinementFcn extends RefinementFcn<T>> = T;

Links

type-refinement-typescript's People

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.