Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Rule proposal: ensure type predicate assignable to narrowed parameter #9768

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

Closed
6 tasks done
kirkwaiblinger opened this issue Aug 9, 2024 · 10 comments
Closed
6 tasks done
Labels
enhancement: new plugin rule New rule request for eslint-plugin locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin wontfix This will not be worked on

Comments

@kirkwaiblinger
Copy link
Member

Before You File a Proposal Please Confirm You Have Done The Following...

My proposal is suitable for this project

  • My proposal specifically checks TypeScript syntax, or it proposes a check that requires type information to be accurate.
  • My proposal is not a "formatting rule"; meaning it does not just enforce how code is formatted (whitespace, brace placement, etc).
  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Description

TS enforces that a type in a type predicate be assignable to the type of the associated parameter.

That is to say

function isNumber(x: number): x is null {
   // implementation irrelevant
}

is illegal, since null cannot be assigned to number. That's a good and sensible check!

Within the function's implementation, though, the parameter's type may become narrowed. I propose we create a rule to report if the function returns true when the predicate type is not assignable to the narrowed type, since that is likely to be an error.

Fail Cases

function isNumber(x: number | null): x is number {
  if (x === null) {
    return true;
//  ~~~~~~~~~~~ proposed lint error: `number` is not assignable to `null`
  } else {
    return false;
  }
}

Pass Cases

function isNumber(x: number | null): x is number {
  if (x === null) {
    return false;
  } else {
    return true;
  }
}

Additional Info

@kirkwaiblinger kirkwaiblinger added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look enhancement: new plugin rule New rule request for eslint-plugin labels Aug 9, 2024
@Josh-Cena
Copy link
Member

I think this is us taking over the type checker's job and I don't think we've ever had similar logic before (probing something's type where there's no expression in code). Do you have a poc that it's doable in the form of a custom rule?

@kirkwaiblinger
Copy link
Member Author

kirkwaiblinger commented Aug 9, 2024

Do you have a poc that it's doable in the form of a custom rule?

Not yet... which is also partially why I hadn't proposed this previously. But, my (optimistic) thinking is that it would be subject to the same or similar detection as detecting The function returns a boolean expression that’s tied to a refinement on the parameter. that will be needed in #9764.

@bradzacher
Copy link
Member

I don't think that this is actually possible to do.
From what I understand it's not possible to ask speculative questions of TS - i.e. we can't ask the question "after this condition, what is the refinement on x?"

Without that power we can't inspect the type in a certain control flow location.

That aside - we'd have to walk TS's control flow graph somehow to "get" the spot immediately before the termination of the branch -- probably doable -- haven't seen TS's control flow APIs though.

Checking if the refined type is exactly (or assignable to) the predicate type is doable now that we have the relationship API.

Though there are a few places that I know I'd immediately disable the rule.
For example we have a few spots that take an object and duck-type it (eg if ('type' in obj)) to call it an AST node because it's a safe assumption.

@JoshuaKGoldberg
Copy link
Member

So, ah, out of scope for us? Close with another rueful wontfix? 😞

@kirkwaiblinger
Copy link
Member Author

kirkwaiblinger commented Aug 28, 2024

Hmm, I get that there may be slightly more control flow analysis to consider, but I'm not quite following why the refinement analysis isn't the same level of difficulty as in #9764. Am I missing something obvious?

If so, totally fine to close!

@bradzacher
Copy link
Member

bradzacher commented Aug 28, 2024

#9764 has a very slim, controlled scope:

  • The function has a single return statement and no implicit returns.
  • The function does not mutate its parameter.
  • The function returns a boolean expression that’s tied to a refinement on the parameter.

All that rule needs to do is validate that the function adheres to those things and if it does, report.

OTOH This issue is about trying to figure out what the type of the refined parameter is at a given return point so that we can ensure that it's assignable to the defined predicate type.
As I mentioned above -- TS doesn't allow us to speculate -- we can't say "hey TS if we were to check the type of x at this line here, what would its type be?". Instead we would need to manually build logic to track the branches and assignments in the function body so we can track changes to the parameter's type and thus hopefully understand what the type of the parameter should be when we see a return statement.

I do love the idea of the rule but yeah I just don't think it's possible for us to build.
This would be a good request for TS upstream though -- like a strictTypeGuard compiler option.

@Josh-Cena
Copy link
Member

I feel like we should document some common limitations of typed rules:

  • You can't query variables that does not have a reference in the source code at a particular place
  • You can't ask TS to redo checking "if the source looks like this instead"
  • Anything else?

@kirkwaiblinger
Copy link
Member Author

kirkwaiblinger commented Aug 28, 2024

Right, but, still, how do you plan to solve that for #9764, then? Like, how would you actually determine that this is an inferrable type predicate?

declare function myTypeGuard(x: unknown): x is string | number;

function maybeInferredTypePredicate(x: unknown) {
  return myTypeGuard(x);
}

(to say nothing of this, which, believe it or not, is inferred as a type predicate)

function maybeInferredTypePredicate(x: unknown) {
  const xIsStringOrNumber = myTypeGuard(x);
  return xIsStringOrNumber;
}

(both in playground)

@bradzacher
Copy link
Member

Like, how would you actually determine that this is an inferrable type predicate?

That rule doesn't specifically care about correctness - it just cares about the rules for inferring the predicate.
So in this instance the function passes the rules:

  • it has a single return statement with no implicit returns
  • it does not mutate any parameters
  • the returned value is a refinement of a parameter.

That last dot point is going to be the complicated bit to solve for sure and I doubt we'll be able to handle all the cases. We'll need to build logic that flags things that look like refinements.

In your examples it's easy cos it's a call to an existing predicate which is an obvious refinement.

Again the important distinction is that #9764 won't need to determine the actual types of things - just that it has to walk backwards from the return statement and go "does it look like a refinement of the parameter?"

@kirkwaiblinger
Copy link
Member Author

Gotcha. Well, I shall wontfix it for now.👍

Thanks for talking through this!

@kirkwaiblinger kirkwaiblinger closed this as not planned Won't fix, can't repro, duplicate, stale Aug 28, 2024
@kirkwaiblinger kirkwaiblinger added wontfix This will not be worked on and removed triage Waiting for team members to take a look labels Aug 28, 2024
@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Sep 5, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement: new plugin rule New rule request for eslint-plugin locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants