Open alita-moore opened 3 years ago
would result in cleaner typing
The clean approach would be to use a type guard and avoid the type assertion (aka "cast") entirely.
Perhaps you can provide a better example?
Also you checked that this wouldn't be a breaking change, but introducing a new global type could very well be breaking for anyone having such a type defined.
@MartinJohns what do you mean by global type? I guess I don't know how this would be implemented from the Typescript side. But I don't know how it would involve a global type.
What do you mean by type guard? Do you mind providing an example
I.e. All current code would already be breaking types if they cast to a typescript function (what do you call them?) that had a required type. In that case it wouldn't infer self, but in the case where you cast to a type with a required type but don't define that type it would cast. So at least assuming the code is syntactically correct right now it would break.
here's a couple examples that may help explain what I mean
type CastTo<T> = T
const array = ["string"]
// currently..
const str = array[0] as CastTo<number>;
str -> number
const str = array[0] as CastTo; // expected required type T
// I propose
const str = array[0] as CastTo<number>;
str -> number
const str = array[0] as CastTo; // i.e. CastTo is seen as CastTo<typeof array[0]> to the linter
str -> string
also it's not always possible to define type as non-nullable without running a potentially null array through a filter to assure the contents are defined.
I think the main reason this may cause issues right now though is because people might not notice that the type their casting to is being called with the implicit type which would be confusing. So maybe this isn't a great idea π€·ββοΈ
in this case considering what I just mentioned it would be better to just have an implicit type available called self.. but now I see what you meant by a global type.. π’
What do you mean by type guard? Do you mind providing an example
function isNotUndefined<T>(value: T | undefined): value is T {
return value !== undefined;
}
const array1 = ["one", undefined, "two"].filter(Boolean); // type is (string | undefined)[]
const array2 = ["one", undefined, "two"].filter(isNotUndefined); // type is string[]
No type-assertion needed. Every type-assertion is a potential issue, because you overwrite the type-system and tell the compiler "shh... trust me, it's this type" (even if this may not be the case).
As proposed, this would be a breaking change for any generic type with a type parameter default. For example, given type Foo<T = string> = ...
, writing 123 as Foo
is currently the same as writing 123 as Foo<string>
, but you are suggesting it should be interpreted as 123 as Foo<123>
instead?
@jcalz it would only apply to required types, Foo<T = string> is an optional type. So it wouldn't affect those as proposed.
Also I encountered another use case where I want to make a type mutable (using type-fest) but I have to first define the const and then redfine it to get that type.
My exact example is
const _context = (await import("github")).context
const context = _context as Mutable<type of _context>
// OR
const context = (await import("github")).context as Mutable<PromiseValue<ReturnType<typeof import("github")>>["context"]>
So my proposed change would instead be this
const context = (await import("github")).context as Mutable
Which is essentially just option 2 but implicit. This implicit type could be called self or something similar as well if you'd like it to be more transparent / control. I.e.
const context = (await import("github")).context as Mutable<self>
Notably self would only be defined in this context similar to how dynamic types are defined in functions and so it would only interfere with other types if you wanted to use anther type called self in a cast. But this seems to me to be a negligible edgecase.
Generally speaking this would clean up a lot of my code
You can achieve something like that trivially using a helper function without weird and confusing type shenanigans:
function asMutable<T>(value: T): Mutable<T> { return value; }
const context = asMutable(await import("github")).context);
You can achieve something like that trivially using a helper function without weird and confusing type shenanigans:
function asMutable<T>(value: T): Mutable<T> { return value; } const context = asMutable(await import("github")).context);
Yeah but it's still adding complexity to my code. Which defeats the purpose
Checking in on this, I still think this would be a helpful addition.
Suggestion
π Search Terms
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
It would be a lot cleaner if when you cast a type to some other type (for example NonNullable) you don't have to specify typeof self.
π Motivating Example
if you have an Array that could be undefined you can filter out undefined like so
but in this case the type would still be
(string | undefined)[]
so it would cause linting errors later in the code. While this may be specific to the .filter method, it may be unreasonable to expect all methods accurately account for type checks like this. So one easy way to fix this would just be to dowhat I'm suggesting is that instead of requiring you do
typeof arrayUndefined
you can instead do an implicit cast to make the code cleanerπ» Use Cases
I would like this because it would result in cleaner typing for unhandled edgecases in the typescript linter