user2154768
user2154768

Reputation: 1130

TypeScript: How to "change" type of method parameter inside method?

I have an invoice that can be in the states New/Invoiced. I have a doInvoicing method that takes a New Invoice and return an Invoiced invoice. But I can't change the state of the invoice in my method, due to it being bound to the New state.

Currently I can only think of casting the input invoice to Invoiced. However, this leaves a hole in the type checking, as it does not verify that I set the invoice state to Invoiced. I am thinking the operation required for this must combine the steps of casting/setting the value (if possible).

doInvoicing(invoice: Invoice & { state: invoiceState.New }):
        Invoice & { state: invoiceState.Invoiced } {

    var invoiced = invoice as Invoice & { state: invoiceState.Invoiced }; 
    invoiced.state = invoiceState.Invoiced;    // This MUST happen, but unverified
    return invoiced;
}

enum invoiceState {
    New, Invoiced
}

Upvotes: 1

Views: 1317

Answers (2)

user2154768
user2154768

Reputation: 1130

I have found a somewhat ok solution that avoids copying. Encapsulate the change in a "setProperty" method:

changeProp<V extends T[K], T, K extends keyof T>(obj:
        { [P in keyof T]: T[P] }, key: K, value: V): T & { [P in K]: V } {

    obj[key] = value;   // The required change is not verified, but at least only 1 copy for entire program
    return obj as T & { [P in K]: V };
}

This has the problem that the returned type is only restricted in V in regard to V type, not the value passed in. It can be stated with explicit generic paramters though:

var invoiced: InvoicedInvoice = this.changeProp<invoiceState.Invoiced, Invoice, "state">(invoice, "state", invoiceState.Invoiced);

Now the issue is that all generic parameters must be stated, not just V. Preferably only V or none of the generic parameters should have to be stated.

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1075467

With the way you have it set up, I don't see a way to do it without creating a new invoice object (or, of course, fudging with type assertions or any):

return {...invoice, state: InvoiceState.Invoiced};

Note that spread does just a shallow copy, which seems like it would be sufficient here, but...

Upvotes: 2

Related Questions