Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when assertion function calls aren't CFA'd #33622

Merged
merged 9 commits into from
Sep 30, 2019
Merged

Error when assertion function calls aren't CFA'd #33622

merged 9 commits into from
Sep 30, 2019

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Sep 26, 2019

With this PR we issue errors when calls to assertion functions are written in a manner that isn't recognized by control flow analysis (see #32695 for the specific requirements). For example:

class Test {
    assert(value: unknown): asserts value {}
}

function f20(x: unknown) {
    const assert1 = (value: unknown): asserts value => {}
    assert1(typeof x === "string");  // Error
    const assert2: (value: unknown) => asserts value = () => {};
    assert2(typeof x === "string");  // Ok
    const t1 = new Test();
    t1.assert(typeof x === "string");  // Error
    const t2: Test = new Test();
    t2.assert(typeof x === "string");  // Ok
    const getAssert = () => assert1;
    getAssert()(typeof x === "string");  // Error
}

The reported errors are:

t.ts(7,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.

t.ts(11,5): error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.

t.ts(15,5): error TS2776: Assertions require the call target to be an identifier or qualified name.

Fixes #33580.

@ahejlsberg ahejlsberg requested review from RyanCavanaugh and sandersn and removed request for RyanCavanaugh September 26, 2019 21:18
@ahejlsberg
Copy link
Member Author

@typescript-bot run dt
@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 27, 2019

Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at a13f862. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 27, 2019

Heya @ahejlsberg, I've started to run the extended test suite on this PR at a13f862. You can monitor the build here. It should now contribute to this PR's status checks.

@DanielRosenwasser
Copy link
Member

@typescript-bot user test this
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 27, 2019

Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at a13f862. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Sep 27, 2019

Heya @DanielRosenwasser, I've started to run the parallelized community code test suite on this PR at a13f862. You can monitor the build here. It should now contribute to this PR's status checks.

Copy link
Member

@DanielRosenwasser DanielRosenwasser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these error messages are very confusing, and we need to do as much as we can to keep it short and make it actionable.

@@ -2726,6 +2726,14 @@
"category": "Error",
"code": 2774
},
"Control flow effects of calls to assertion and never-returning functions are reflected only when every variable or property referenced in the function expression is declared with an explicit type annotation.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what this is telling a user. Is it something like the following?

This assertion call cannot be properly analyzed because not every variable and property used in the current function has an explicit type annotation.

This 'never'-returning function call cannot be properly analyzed because not every variable and property used in the current function has an explicit type annotation.

Along with

This assertion call cannot be properly analyzed because the function being called is not a qualified name.

This 'never'-returning function call cannot be properly analyzed because the function being called is not a qualified name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think that "explicitly never-returning function" is really just another kind of assertion. We could introduce the concept of guard = "explicitly never-returning function" if it's too ambiguous.
  2. Mentioning control flow isn't necessary for fixing the error -- and presumably most people understand the concept of assertion better than control flow anyway.
  3. As for @DanielRosenwasser's wording: "This assertion call" is implied by the error span. The fact that it's an assertion is implied by the error span+error wording. So neither of those is necessary. Wording this categorically also allows us to flip the message back to positive.
Assertions must be an identifier or qualified name.
Assertions must reference a declaration with an explicit type annotation.
  Related: This is the declaration that should be annotated.
  1. We know that the expression has type annotations because hasTypePredicateOrNeverReturnType(signature) is true.
  2. If we want to handle the case where there is more than one declaration, the message can be
Assertions must reference declaration(s) with an explicit type annotation.
  Related: This is one declaration that must be annotated.
  Related: This is one declaration that must be annotated.
  :  
  : 

const assert = (value: unknown): asserts value => {}
assert(typeof x === "string"); // Error
~~~~~~
!!! error TS2775: Control flow effects of calls to assertion and never-returning functions are reflected only when every variable or property referenced in the function expression is declared with an explicit type annotation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which variable doesn't have an annotation? Can you give a related span?

@typescript-bot
Copy link
Collaborator

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

@jack-williams
Copy link
Collaborator

A couple of candidate messages.

assert1(typeof x === "string");  // Error
// Assertion calls must be explicitly typed.
// Identifier 'assert1' lacks an explicit type annotation and is not subject to control flow analysis.
getAssert()(typeof x === "string");  // Error
// Assertion calls must be explicitly resolved.
// Function expression is not an identifier or qualified-name and is not subject to control flow analysis.

@@ -2726,6 +2726,14 @@
"category": "Error",
"code": 2774
},
"Control flow effects of calls to assertion and never-returning functions are reflected only when every variable or property referenced in the function expression is declared with an explicit type annotation.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think that "explicitly never-returning function" is really just another kind of assertion. We could introduce the concept of guard = "explicitly never-returning function" if it's too ambiguous.
  2. Mentioning control flow isn't necessary for fixing the error -- and presumably most people understand the concept of assertion better than control flow anyway.
  3. As for @DanielRosenwasser's wording: "This assertion call" is implied by the error span. The fact that it's an assertion is implied by the error span+error wording. So neither of those is necessary. Wording this categorically also allows us to flip the message back to positive.
Assertions must be an identifier or qualified name.
Assertions must reference a declaration with an explicit type annotation.
  Related: This is the declaration that should be annotated.
  1. We know that the expression has type annotations because hasTypePredicateOrNeverReturnType(signature) is true.
  2. If we want to handle the case where there is more than one declaration, the message can be
Assertions must reference declaration(s) with an explicit type annotation.
  Related: This is one declaration that must be annotated.
  Related: This is one declaration that must be annotated.
  :  
  : 

@sandersn
Copy link
Member

Oh, I didn't read @jack-williams comments until after commenting. Basically, what he said. =)

@DanielRosenwasser
Copy link
Member

Mentioning control flow isn't necessary for fixing the error

Agreed!

"This assertion call" is implied by the error span.

Yes and no - I find it easier to just have the error message tell me what's wrong rather than reconstructing the intent from the span as well.

Wording this categorically also allows us to flip the message back to positive.

The suggestions both of you have are much more digestible, and made it clear to me exactly what the error check is. I only have nits at this point. :)

What do you think of:

Assertion functions can only be called when they were declared with an explicit type annotation.
Related: This is the declaration that must be annotated.

and

Assertion functions can only be called when referenced by an identifier or qualified name.

?

@sandersn
Copy link
Member

No, I like the shorter wording better. The longer message only improves the precision of the wording, but won't help people (especially naive people) track down the problem.

  1. "A declaration with an explicit type annotation" is shorter and therefore clearer than "When they [functions] were declared with an explicit type annotation", and much more flexible because it's a noun phrase. Just use it as the object of the sentence!
  2. I guess that you ended up with "Assertion functions" because of "can only be called", which you got because of "when they were declared ...". Whether that reconstruction is right or not, it makes it sound like "an assertion function" could be different from "an assertion", but we don't actually have those two concepts. So we might as well call the one concept we do have "an assertion".

assert(typeof x === "string"); // Error
~~~~~~
!!! error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
!!! related TS2728 tests/cases/conformance/controlFlow/assertionTypePredicates1.ts:128:11: 'assert' is declared here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'{0}' must be annotated would be a lot nicer 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would, but do we really want yet another diagnostic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you already merged this, but for the future, diagnostics are cheap! They bear very little overhead for the <20 of us on the core team compared to at least hundreds of thousands of TypeScript users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed in the team room and agreed that the existing message is fine, so I didn't change it.

@ahejlsberg
Copy link
Member Author

Latest commits exclude never-returning functions from check that they match CFA requirements and improve error messages, per discussion in today's design meeting.

@ahejlsberg ahejlsberg dismissed sandersn’s stale review September 27, 2019 22:45

Error messages now improved

@@ -4062,6 +4062,12 @@ namespace ts {
return node.kind === SyntaxKind.Identifier || isPropertyAccessEntityNameExpression(node);
}

export function isDottedName(node: Expression): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, will this need to be updated for optional chains? Should a t1?.assert(b) be a thing?

@ahejlsberg ahejlsberg changed the title Error when assertion and never-returning function calls aren't CFA'd Error when assertion function calls aren't CFA'd Sep 28, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.7.0 milestone Sep 30, 2019
@mgcrea
Copy link

mgcrea commented Jan 16, 2020

Strangely, with 3.8-beta (or 3.7-stable), this will trigger the Assertions require every name in the call target to be declared with an explicit type annotation. ts(2775) error:

export const assert = (value: unknown, message?: string): asserts value => {
  if (!value) {
    throw new AssertionError({message});
  }
};

While this does work properly:

export const assert: (value: unknown, message?: string) => asserts value = (value: unknown, message?: string) => {
  if (!value) {
    throw new AssertionError({message});
  }
};

Is this working as intended?

@DanielRosenwasser DanielRosenwasser deleted the fix33580 branch July 30, 2020 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CFA Assertions Not Active on Implicitly-Typed Function Calls
8 participants