Skip to content

TypeScripts Type System is Turing Complete#14833

@hediet

Description

@hediet

This is not really a bug report and I certainly don't want TypeScripts type system being restricted due to this issue. However, I noticed that the type system in its current form (version 2.2) is turing complete.

Turing completeness is being achieved by combining mapped types, recursive type definitions, accessing member types through index types and the fact that one can create types of arbitrary size.
In particular, the following device enables turing completeness:

typeMyFunc<TArg>={"true": TrueExpr<MyFunction,TArg>,"false": FalseExpr<MyFunc,TArg>}[Test<MyFunc,TArg>];

with TrueExpr, FalseExpr and Test being suitable types.

Even though I didn't formally prove (edit: in the meantime, I did - see below) that the mentioned device makes TypeScript turing complete, it should be obvious by looking at the following code example that tests whether a given type represents a prime number:

typeStringBool="true"|"false";interfaceAnyNumber{prev?: any,isZero: StringBool};interfacePositiveNumber{prev: any,isZero: "false"};typeIsZero<TNumberextendsAnyNumber>=TNumber["isZero"];typeNext<TNumberextendsAnyNumber>={prev: TNumber,isZero: "false"};typePrev<TNumberextendsPositiveNumber>=TNumber["prev"];typeAdd<T1extendsAnyNumber,T2>={"true": T2,"false": Next<Add<Prev<T1>,T2>>}[IsZero<T1>];// Computes T1 * T2typeMult<T1extendsAnyNumber,T2extendsAnyNumber>=MultAcc<T1,T2,_0>;typeMultAcc<T1extendsAnyNumber,T2,TAccextendsAnyNumber>={"true": TAcc,"false": MultAcc<Prev<T1>,T2,Add<TAcc,T2>>}[IsZero<T1>];// Computes max(T1 - T2, 0).typeSubt<T1extendsAnyNumber,T2extendsAnyNumber>={"true": T1,"false": Subt<Prev<T1>,Prev<T2>>}[IsZero<T2>];interfaceSubtResult<TIsOverflowextendsStringBool,TResultextendsAnyNumber>{isOverflowing: TIsOverflow;result: TResult;}// Returns a SubtResult that has the result of max(T1 - T2, 0) and indicates whether there was an overflow (T2 > T1).typeSafeSubt<T1extendsAnyNumber,T2extendsAnyNumber>={"true": SubtResult<"false",T1>,"false": {"true": SubtResult<"true",T1>,"false": SafeSubt<Prev<T1>,Prev<T2>>}[IsZero<T1>]}[IsZero<T2>];type_0={isZero: "true"};type_1=Next<_0>;type_2=Next<_1>;type_3=Next<_2>;type_4=Next<_3>;type_5=Next<_4>;type_6=Next<_5>;type_7=Next<_6>;type_8=Next<_7>;type_9=Next<_8>;typeDigits={0: _0,1: _1,2: _2,3: _3,4: _4,5: _5,6: _6,7: _7,8: _8,9: _9};typeDigit=0|1|2|3|4|5|6|7|8|9;typeNumberToType<TNumberextendsDigit>=Digits[TNumber];// I don't know why typescript complains here.type_10=Next<_9>;type_100=Mult<_10,_10>;typeDec2<T2extendsDigit,T1extendsDigit>=Add<Mult<_10,NumberToType<T2>>,NumberToType<T1>>;functionforceEquality<T1,T2extendsT1>(){}functionforceTrue<Textends"true">(){}//forceTrue<Equals< Dec2<0,3>, Subt<Mult<Dec2<2,0>, _3>, Dec2<5,7>> >>();//forceTrue<Equals< Dec2<0,2>, Subt<Mult<Dec2<2,0>, _3>, Dec2<5,7>> >>();typeMod<TNumberextendsAnyNumber,TModNumberextendsAnyNumber>={"true": _0,"false": Mod2<TNumber,TModNumber,SafeSubt<TNumber,TModNumber>>}[IsZero<TNumber>];typeMod2<TNumberextendsAnyNumber,TModNumberextendsAnyNumber,TSubtResultextendsSubtResult<any,any>>={"true": TNumber,"false": Mod<TSubtResult["result"],TModNumber>}[TSubtResult["isOverflowing"]];typeEquals<TNumber1extendsAnyNumber,TNumber2extendsAnyNumber>=Equals2<TNumber1,TNumber2,SafeSubt<TNumber1,TNumber2>>;typeEquals2<TNumber1extendsAnyNumber,TNumber2extendsAnyNumber,TSubtResultextendsSubtResult<any,any>>={"true": "false","false": IsZero<TSubtResult["result"]>}[TSubtResult["isOverflowing"]];typeIsPrime<TNumberextendsPositiveNumber>=IsPrimeAcc<TNumber,_2,Prev<Prev<TNumber>>>;typeIsPrimeAcc<TNumber,TCurrentDivisor,TCounterextendsAnyNumber>={"false": {"true": "false","false": IsPrimeAcc<TNumber,Next<TCurrentDivisor>,Prev<TCounter>>}[IsZero<Mod<TNumber,TCurrentDivisor>>],"true": "true"}[IsZero<TCounter>];forceTrue<IsPrime<Dec2<1,0>>>();forceTrue<IsPrime<Dec2<1,1>>>();forceTrue<IsPrime<Dec2<1,2>>>();forceTrue<IsPrime<Dec2<1,3>>>();forceTrue<IsPrime<Dec2<1,4>>>();forceTrue<IsPrime<Dec2<1,5>>>();forceTrue<IsPrime<Dec2<1,6>>>();forceTrue<IsPrime<Dec2<1,7>>>();

Besides (and a necessary consequence of being turing complete), it is possible to create an endless recursion:

type Foo<T extends "true", B> ={"true": Foo<T, Foo<T, B>>}[T]; let f: Foo<"true",{}> = null!; 

Turing completeness could be disabled, if it is checked that a type cannot use itself in its definition (or in a definition of an referenced type) in any way, not just directly as it is tested currently. This would make recursion impossible.

//edit:
A proof of its turing completeness can be found here

Metadata

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions