Answers to Add
In addition to the comprehensive response from @DemiPixel; after examining the referenced documentation:
Object literals are subject to special treatment and undergo excess property checking when assigned to other variables or passed as arguments. If an object literal has properties not found in the "target type," an error will occur.
This concept is further supported by the detailed error message you encountered while running tsc
:
test.ts:9:9 - error TS2322: Type '{ id: number; name: string; }' is not assignable to type 'Pick<TestInterface, "id">'.
Object literal may only specify known properties, and 'name' does not exist in type 'Pick<TestInterface, "id">'.
9 name: "projectName",
~~~~~~~~~~~~~~~~~~~
Found 1 error.
General Observations on Type Systems
In contemplating this matter, I wanted to share my perspective on type systems and why I believe the level of flexibility (except for object literals) makes sense.
From my experience, types generally define the inherent capabilities of an instance but do not necessarily restrict them. For example, consider reproducing the second function from your initial code:
interface TestInterface {
id: number
name: string
}
function tmp2(): Pick<TestInterface, "id"> {
const data = {
id: 123,
name: "projectName",
}
return data
}
When invoking this function, TypeScript mandates that the returned value is capable of responding to accessing the id
property at a minimum (specifically, of type { id: number }
).
As demonstrated above, if we have:
const result = tmp2()
console.log(result.id)
console.log(result.name)
The attempt to use result.name
will result in an error:
test.ts:16:20 - error TS2339: Property 'name' does not exist on type 'Pick<TestInterface, "id">'.
16 console.log(result.name)
~~~~
Found 1 error.
At a fundamental level, it seems logical to constrain the value returned within the function, which appears to be the safest approach. However, TypeScript essentially disregards any properties beyond those specified in the return type, rendering any access to such properties illegal.
Transpilation Challenges and Object Literals
Now, turning to the specific scenario of returning an object literal; one could speculate that this restriction was implemented to address potential issues when calling transpiled TypeScript code from JavaScript. Consider a situation where your code is exported from a module and then imported and utilized in the following manner:
import tmp2 from './test'
const result = tmp2()
console.log(result.id)
console.log(result.name)
Similar to before, TypeScript views result
as { id: number }
, resulting in the same error. Nonetheless, it is entirely permissible to import the identical transpiled module (remember, it's just a JavaScript module) from JavaScript using the same syntax, yielding:
123
projectName
Hence, this scrutiny of object literals for excess properties prevents such usage scenarios. In essence, object literals represent the sole instances where TypeScript can effectively apply this type of check; other instances originate from external sources during runtime, such as the network, storage, or user input. Static type checks during transpilation cannot safeguard against these containing surplus properties.
Conclusion
In light of both the theoretical soundness of a comprehensive type system and the hybrid nature intrinsic to TypeScript transpilation, is it essential to confine the definition of object literals to exact properties expected for assignment or usage? The necessity may hinge on the likelihood of utilizing parts of your project's transpiled output in a native JavaScript context. I argue that for the majority of TypeScript projects, reliance solely on TypeScript without such restrictions would suffice.