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) } 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 { 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');