Steps to reproduce
tsconfig.json:
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true
}
}
index.ts:
interface TypeData {
ref?: string;
}
interface UsageData {
Type_Data?: Array<TypeData>;
Comments?: string;
}
interface EmailAddressData {
Email_Address: string;
Usage_Data?: Array<UsageData>;
}
declare function condition(): boolean;
declare function makeRef(s: string): string;
declare const emails: { value: string; isWork: boolean }[];
async function build(): Promise<EmailAddressData[]> {
let emailAddressData;
if (condition()) {
const primary = emails[0];
emailAddressData = primary
? [
{
Email_Address: primary.value,
Usage_Data: [
{
attributes: { "wd:Public": false },
Type_Data: [
{
attributes: { "wd:Primary": true },
ref: makeRef("HOME"),
},
],
},
],
},
]
: [];
} else {
emailAddressData = emails.map((email) => ({
Email_Address: email.value,
Usage_Data: [
{
attributes: { "wd:Public": email.isWork },
Type_Data: [
{
attributes: { "wd:Primary": true },
ref: makeRef("HOME"),
},
],
},
],
}));
}
return emailAddressData;
}
build().then(console.log);
Tested with @typescript/native-preview@beta (Version 7.0.0-dev.20260421.2) and typescript@6.0.3.
The attributes field doesn't exist on UsageData or TypeData — but neither does it exist in any of the literals' contextual type at the literal expression sites, because both branches assign to the same untyped let and the contextual type only kicks in at the return statement.
Behavior with typescript@6.0
Compiles cleanly (exit 0, no diagnostics). Excess-property checking does not fire on the nested literals because they are assigned to an untyped let variable and the contextual type only flows in at return emailAddressData; (where excess-prop checking is no longer "fresh").
Behavior with tsgo
index.ts(29,17): error TS2353: Object literal may only specify known properties, and 'attributes' does not exist in type 'UsageData'.
Note that:
- Only the first
attributes: { "wd:Public": false } (the if (condition()) branch) is flagged. The matching attributes field in the else branch is not flagged.
- The inner
attributes: { "wd:Primary": true } on TypeData is not flagged in either branch.
That asymmetric reporting suggests tsgo is propagating the contextual type back into one of the conditional branches (the primary ? [...] : [] ternary that's inside the if-true branch) but not into emails.map((email) => ({...})) in the else branch. tsc treats both branches consistently.
Steps to reproduce
tsconfig.json:{ "compilerOptions": { "strict": true, "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "noEmit": true } }index.ts:Tested with
@typescript/native-preview@beta(Version 7.0.0-dev.20260421.2) andtypescript@6.0.3.The
attributesfield doesn't exist onUsageDataorTypeData— but neither does it exist in any of the literals' contextual type at the literal expression sites, because both branches assign to the same untypedletand the contextual type only kicks in at thereturnstatement.Behavior with
typescript@6.0Compiles cleanly (exit 0, no diagnostics). Excess-property checking does not fire on the nested literals because they are assigned to an untyped
letvariable and the contextual type only flows in atreturn emailAddressData;(where excess-prop checking is no longer "fresh").Behavior with
tsgoNote that:
attributes: { "wd:Public": false }(theif (condition())branch) is flagged. The matchingattributesfield in theelsebranch is not flagged.attributes: { "wd:Primary": true }onTypeDatais not flagged in either branch.That asymmetric reporting suggests tsgo is propagating the contextual type back into one of the conditional branches (the
primary ? [...] : []ternary that's inside the if-true branch) but not intoemails.map((email) => ({...}))in the else branch. tsc treats both branches consistently.