From 46b2c36abcb9e2f9bd8fdb7ae39178d8d88c6276 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 1 Oct 2025 14:26:20 -0700 Subject: [PATCH 1/3] Delete extraneous diagnostic --- internal/checker/checker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index d8de6b0419..ca40aafc20 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -8053,7 +8053,6 @@ func (c *Checker) checkCallExpression(node *ast.Node, checkMode CheckMode) *Type if !ast.IsDottedName(node.Expression()) { c.error(node.Expression(), diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name) } else if c.getEffectsSignature(node) == nil { - c.error(node.Expression(), diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation) diagnostic := c.error(node.Expression(), diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation) c.getTypeOfDottedName(node.Expression(), diagnostic) } From 1ca41eed1ad46351fed5ebd3aca61553028a5f44 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 1 Oct 2025 14:27:16 -0700 Subject: [PATCH 2/3] Permit expando property function with return type annotation as assertion --- internal/checker/flow.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/checker/flow.go b/internal/checker/flow.go index 3ebbf9bf54..6ff6acbf4c 100644 --- a/internal/checker/flow.go +++ b/internal/checker/flow.go @@ -2160,7 +2160,17 @@ func (c *Checker) getExplicitTypeOfSymbol(symbol *ast.Symbol, diagnostic *ast.Di } func (c *Checker) isDeclarationWithExplicitTypeAnnotation(node *ast.Node) bool { - return (ast.IsVariableDeclaration(node) || ast.IsPropertyDeclaration(node) || ast.IsPropertySignatureDeclaration(node) || ast.IsParameter(node)) && node.Type() != nil + return (ast.IsVariableDeclaration(node) || ast.IsPropertyDeclaration(node) || ast.IsPropertySignatureDeclaration(node) || ast.IsParameter(node)) && node.Type() != nil || + c.isExpandoPropertyFunctionWithReturnTypeAnnotation(node) +} + +func (c *Checker) isExpandoPropertyFunctionWithReturnTypeAnnotation(node *ast.Node) bool { + if ast.IsBinaryExpression(node) { + if expr := node.AsBinaryExpression().Right; ast.IsFunctionLike(expr) && expr.Type() != nil { + return true + } + } + return false } func (c *Checker) hasTypePredicateOrNeverReturnType(sig *Signature) bool { From d6f12eb4d686839e8d62a4547a5336c838ba49a8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 1 Oct 2025 14:31:23 -0700 Subject: [PATCH 3/3] Add regression test --- .../expandoFunctionAsAssertion.symbols | 27 +++++++++++++++ .../compiler/expandoFunctionAsAssertion.types | 34 +++++++++++++++++++ .../compiler/expandoFunctionAsAssertion.ts | 12 +++++++ 3 files changed, 73 insertions(+) create mode 100644 testdata/baselines/reference/compiler/expandoFunctionAsAssertion.symbols create mode 100644 testdata/baselines/reference/compiler/expandoFunctionAsAssertion.types create mode 100644 testdata/tests/cases/compiler/expandoFunctionAsAssertion.ts diff --git a/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.symbols b/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.symbols new file mode 100644 index 0000000000..a046544871 --- /dev/null +++ b/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.symbols @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/expandoFunctionAsAssertion.ts] //// + +=== expandoFunctionAsAssertion.ts === +function example() {} +>example : Symbol(example, Decl(expandoFunctionAsAssertion.ts, 0, 0)) + +example.isFoo = function isFoo(value: string): asserts value is 'foo' { +>example.isFoo : Symbol(example.isFoo, Decl(expandoFunctionAsAssertion.ts, 0, 21)) +>example : Symbol(example, Decl(expandoFunctionAsAssertion.ts, 0, 0)) +>isFoo : Symbol(example.isFoo, Decl(expandoFunctionAsAssertion.ts, 0, 21)) +>isFoo : Symbol(isFoo, Decl(expandoFunctionAsAssertion.ts, 2, 15)) +>value : Symbol(value, Decl(expandoFunctionAsAssertion.ts, 2, 31)) +>value : Symbol(value, Decl(expandoFunctionAsAssertion.ts, 2, 31)) + + if (value !== 'foo') { +>value : Symbol(value, Decl(expandoFunctionAsAssertion.ts, 2, 31)) + + throw new Error('Not foo'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +}; + +example.isFoo('test'); +>example.isFoo : Symbol(example.isFoo, Decl(expandoFunctionAsAssertion.ts, 0, 21)) +>example : Symbol(example, Decl(expandoFunctionAsAssertion.ts, 0, 0)) +>isFoo : Symbol(example.isFoo, Decl(expandoFunctionAsAssertion.ts, 0, 21)) + diff --git a/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.types b/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.types new file mode 100644 index 0000000000..b5835b1efa --- /dev/null +++ b/testdata/baselines/reference/compiler/expandoFunctionAsAssertion.types @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/expandoFunctionAsAssertion.ts] //// + +=== expandoFunctionAsAssertion.ts === +function example() {} +>example : { (): void; isFoo: (value: string) => asserts value is "foo"; } + +example.isFoo = function isFoo(value: string): asserts value is 'foo' { +>example.isFoo = function isFoo(value: string): asserts value is 'foo' { if (value !== 'foo') { throw new Error('Not foo'); }} : (value: string) => asserts value is "foo" +>example.isFoo : (value: string) => asserts value is "foo" +>example : { (): void; isFoo: (value: string) => asserts value is "foo"; } +>isFoo : (value: string) => asserts value is "foo" +>function isFoo(value: string): asserts value is 'foo' { if (value !== 'foo') { throw new Error('Not foo'); }} : (value: string) => asserts value is "foo" +>isFoo : (value: string) => asserts value is "foo" +>value : string + + if (value !== 'foo') { +>value !== 'foo' : boolean +>value : string +>'foo' : "foo" + + throw new Error('Not foo'); +>new Error('Not foo') : Error +>Error : ErrorConstructor +>'Not foo' : "Not foo" + } +}; + +example.isFoo('test'); +>example.isFoo('test') : void +>example.isFoo : (value: string) => asserts value is "foo" +>example : { (): void; isFoo: (value: string) => asserts value is "foo"; } +>isFoo : (value: string) => asserts value is "foo" +>'test' : "test" + diff --git a/testdata/tests/cases/compiler/expandoFunctionAsAssertion.ts b/testdata/tests/cases/compiler/expandoFunctionAsAssertion.ts new file mode 100644 index 0000000000..7cf3c2f2fd --- /dev/null +++ b/testdata/tests/cases/compiler/expandoFunctionAsAssertion.ts @@ -0,0 +1,12 @@ +// @strict: true +// @noEmit: true + +function example() {} + +example.isFoo = function isFoo(value: string): asserts value is 'foo' { + if (value !== 'foo') { + throw new Error('Not foo'); + } +}; + +example.isFoo('test');