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

📚 New no-evolving-any rule documentation lacks proper examples and/or references #48

Closed
1 task done
bgenia opened this issue Mar 23, 2024 · 9 comments · Fixed by biomejs/biome#2959
Closed
1 task done

Comments

@bgenia
Copy link

bgenia commented Mar 23, 2024

Documentation URL

https://biomejs.dev/linter/rules/no-evolving-any

Description

Follow up on biomejs/biome#1883 biomejs/biome#2112

The docs currently state that evolving types may lead to variables with an any type, but lack any sort of examples which demonstrate this. It is also stated that evolving any disables many type-checking rules, also without any examples or references.

Expectations

What are the exact caveats of using evolving any? What are the examples?

From what I know, evolving any doesn't turn into any if not explicitly assigned as such. Just like it doesn't disable many type-checking rules. I might be wrong, but the docs lack any proofs or references.

Code of Conduct

  • I agree to follow Biome's Code of Conduct
@Sec-ant
Copy link
Member

Sec-ant commented Mar 27, 2024

I don't have enough knowledge on this rule, please allow me to tag @Conaclos @fujiyamaorange to make sure this issue is noticed.

@fujiyamaorange
Copy link

@Sec-ant
Thank you for noticing me. 👍

I'm not sure about @Conaclos's perspective on this matter, but I'd like to offer my own insight.

Consider the following code snippet:

const b = [];
b.push(1);
console.log(b);

In this instance, TypeScript infers the type of b as any[], not as strictly typed array.

Regarding the next example,

let c = null;
c = 'hello';
console.log(c);

Here, TypeScript infers c as having the type any once again. This demonstrates how TypeScript handles type inference in these scenarios."

@ematipico
Copy link
Member

@bgenia you were involved in the original issue, so you have some context and knowledge. If so, would you like to send a PR to add some more explanation and examples?

@fujiyamaorange feel free to send a PR that adds more examples.

@fujiyamaorange
Copy link

@ematipico
Okay! I'll add some examples.

@Sec-ant
Copy link
Member

Sec-ant commented Mar 27, 2024

There might be some misunderstanding. We need no hurry. I think what @bgenia suggests is evolving any shouldn't be banned at all if the user set noImplicitAny to true. Because when noImplicitAny is set to true, TypeScript will leverage the evolving any mechanism to make sure the variable is properly typed.

The examples @fujiyamaorange proposed here will all be properly typed if noImplicityAny is set to true in tsconfig: https://www.typescriptlang.org/play?#code/MYewdgzgLgBARjAvDA2gXQNwFgBQcB0ADgK4QAWAFAIwCU2OokIANgKb7MgDmFcdMAegEwoAT0KsQAM3gwAlhBhhiAWzisATuly42sYEiXFmzegeQByMqxMgLZ8BBbtOPYPyEjxkmQYUxoDTkwLiA

To make things clear, evolving any is evolving from any type to a concrete type, not evolving to an any type. So noEvolvingAny sounds like a rule to prevent a user from declaring variables with an implicit any type that can evolve into a concrete type by TypeScript. And what OP asking is why this is not allowed?

@Conaclos
Copy link
Member

Unfortunately, we are not able to know if noImplicitAny is enabled or disabled.
The rule "assumes" that it is disabled.

Even when it is enabled, you still have some potential issues. Because you allow the type to evolve in any direction.
For example, the following code is correct:

let x = [];
x[0] = 0;
x[0] = "a";
x; //: (number | string)[]

Here is a summary of the inferred type according to TS settings:

code noImplicitAny=true noImplicitAny=false strictNullCheck=true noImplicitAny=false strictNullCheck=false
let x; x; any evolves to undefined never ⚠️ any ⚠️
let x; x=0; x; any evolves to number any ⚠️ any ⚠️
let xs=[]; x; any[] never[] ⚠️ any[] ⚠️
let xs=[]; x[0]=0; xs; any[] evolves to number[] error any[] ⚠️

I agree that the rule documentation should be updated.

@bgenia
Copy link
Author

bgenia commented Mar 27, 2024

I think what @bgenia suggests is evolving any shouldn't be banned at all if the user set noImplicitAny to true. Because when noImplicitAny is set to true, TypeScript will leverage the evolving any mechanism to make sure the variable is properly typed.

Yes, this is my point. Evolving any is only a thing when noImplicitAny is true, otherwise it's just regular any. And evolving any, despite having "any" in it's name, is type safe (Well, as type safe as you can get in a fundamentally unsound language).

Unfortunately, we are not able to know if noImplicitAny is enabled or disabled. The rule "assumes" that it is disabled.

Then this is not a no-evolving-any rule, because evolving any does not exist in such case. I would argue that such rule is not needed in the first place because users can just turn on noImplicitAny. The rule should assume that noImplicitAny is true. IMO a linter should provide checks which the compiler does not, instead of duplicating the existing checks in a weird way.

Even when it is enabled, you still have some potential issues. Because you allow the type to evolve in any direction.
For example, the following code is correct:

let x = [];
x[0] = 0;
x[0] = "a";
x; //: (number | string)[]

So what are the issues here? The code is correct. Type system will only allow you to use x correctly. I still don't get how this is unsafe.

let xs=[] => any[]

While it seems like any[], you can't actually use it as one.

let xs = []


let y: number[] = xs // Error: Variable 'xs' implicitly has an 'any[]' type

@Conaclos
Copy link
Member

Conaclos commented Mar 28, 2024

Then this is not a no-evolving-any rule, because evolving any does not exist in such case.

You made a good point.

So what are the issues here? The code is correct. Type system will only allow you to use x correctly. I still don't get how this is unsafe.

This looks like an error of mixing types. However, I accept that this is mostly a niche issue. This should certainly not justify the existence of the rule.

let x = [];
x[0] = 0;
x[0] = "a";
accept(x);
function accept(x: any[]) {}

The rule should assume that noImplicitAny is true. IMO a linter should provide checks which the compiler does not, instead of duplicating the existing checks in a weird way.

The reason d'être of this rule is a request of @jpike88 (#381).

What I suggest:

  • We could rename the rule (open to suggestion)
  • We should update the rule docs and points to noImplicitAny tsconfig setting.
  • If the TypeScript team decides to turn on by default noImplicitAny in TS 6.0, we could deprecate the rule.

@ematipico ematipico transferred this issue from biomejs/biome Apr 16, 2024
@ematipico
Copy link
Member

@Conaclos, what are the next steps to close this issue? Do we need to update the documentation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants