4

I'm running into an issue with TypeScript whereby an object using one type can be injected via a destructing assignment into an another object that uses a completely different type, and TypeScript doesn't show any errors for it.

An example being:

interface Pizza {
    cookingTime: number;
    size: "Small" | "Medium" | "Large";
}

interface Car {
    modelName: string;
    yearManufactured: number;
}

const produceMalformedPizza = (car: Car): Pizza => {
    return {
        ...car, // This is not a "Pizza" object but no errors are raised
        cookingTime: 10,
        size: "Small",
    }
}

console.log(
    produceMalformedPizza({
        modelName: "NotAPizza",
        yearManufactured: 1980
    })
)

/* Output:
[LOG]: {
  "modelName": "NotAPizza",
  "yearManufactured": 1980,
  "cookingTime": 10,
  "size": "Small"
} 
*/

I've tried to turn every possible strict flag on — and you can see it compiling online here.

If I try to return any specific properties in the produceMalformedPizza function, then the compiler raises errors, as expected — but it seems as though I can inject arbitrary object data using a destructuring (spread) assignment.

I'm relatively new to TypeScript, so perhaps this is expected behaviour? I.e. I can't use object spread destructuring with inferred type safety?

Edit:

As per the answer from @T.J.Crowder this is intentional.

1

1 Answer 1

3

It's expected behavior. The object you're returning is a subtype of Pizza — it has all the properties Pizza requires (plus some that it doesn't).

TypeScript does do "excess property checks" but only in limited situations. For instance, if you were to put a non-Pizza property explicitly in that object literal, TypeScript would flag that up not because it's actually wrong from a type perspective (it's fine to use a subtype) but because it's probably a programmer mistake.

I'm slightly surprised it doesn't do that with ...car (since car has required properties), but it doesn't do excess property checks in all situations.

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

7 Comments

It looks like destructuring ...car is not treated as literal object, hence excess property checks are not applied. AFAIK, it is safe behavior
@captain-yossarian - Yup, but it would make sense to me for the same "pragmatic" excess property check to apply to spread properties as explicit properties. But perhaps it's too costly in terms of implementation/runtime.
I think this is because of mutable nature of js objects. Good news, you can't use ...car at the end :)
@captain-yossarian - I'm afraid I don't understand. car has a definite type, with required properties that aren't part of Pizza, so including ...car in the literal isn't fundamentally different from including someNonPizzaProperty: "x" in it. (Not sure what you mean about ...car at the end, return { cookingTime: 10, size: "Small", ...car } also raises no flags.)
Sorry, I told you about using car at the end, I was wrong, it does not trigger an errors. My example was completely changed, that's why I got an error
|

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.