1

I'm quite a newbie to TypeScript with a strong C# background.

I wonder what's the exact reasons that the type inference doesn't seem to work in the following situation in TypeScript but does in C#:

TypeScript:

interface IResult { }

interface IRequest<TResult extends IResult> { }

interface ISomeResult extends IResult {
    prop: string;
}

interface ISomeRequest extends IRequest<ISomeResult> { }

 function Process<TResult extends IResult>(request: IRequest<TResult>): TResult {
    throw "Not implemented";
}

let request: ISomeRequest = {};
let result = Process(request);
// Error: Property 'prop' does not exist on type '{}'.
console.log(result.prop);

C#

interface IResult { }

interface IRequest<TResult> where TResult : IResult { }

interface ISomeResult : IResult
{
    string Prop { get; set; }
}

interface ISomeRequest : IRequest<ISomeResult> { }

static TResult Process<TResult>(IRequest<TResult> request) where TResult : IResult
{
    throw new NotImplementedException();
}

static void Main()
{
    ISomeRequest request = default;
    var result = Process(request);
    // OK
    Console.WriteLine(result.Prop);
}

Is this an issue of the TS compiler's type inference algorithm (maybe it's not there yet?) or is there some fundamental reason which I am missing and makes this impossible in TS?

1 Answer 1

1

Typescript has a structural type system (this might seem a bit weird when coming from C#, I know I had the same issue 😊). This means that is you have an unused type parameter, it will be ignored by the compiler as it does no really matter for type compatibility. Add any member that uses TResult to IRequest and it works as you expect it to:

interface IResult { }

interface IRequest<TResult extends IResult> { 
    _r?: undefined | TResult
}

interface ISomeResult extends IResult {
    prop: string;
}

interface ISomeRequest extends IRequest<ISomeResult> { }

function Process<TResult extends IResult>(request: IRequest<TResult>): TResult {
    throw "Not implemented";
}

let request: ISomeRequest = { };
let result = Process(request);
console.log(result.prop);

FAQ explaining some o this.

Sign up to request clarification or add additional context in comments.

3 Comments

Wow, you were blazingly fast, thanks! This makes it all clear. However, the workaround seems somewhat hacky and the FAQ saying "In general, you should never have type parameters which are unused. The type will have unexpected compatibility (as shown here) and will also fail to have proper generic type inference in function calls." suggests me that I'm better off not going down this path.
@AdamSimon that is up to you, adding members does work and I have used it sometimes to satisfy the type system. It would be ideal if you could find a meaningful use for the result in the request .. maybe an optional convert function or something .. that could potentially be used in a meaningful way.
Actually this solution only came into the picture to express the relationship between request and result types more strongly than a simple naming convention. Of course, as a nice side effect, it'd allow me to write a bit less code as I wouldn't need to type out the result type when calling Process. So after all it could be ok to use this harmless trick.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.