-
Notifications
You must be signed in to change notification settings - Fork 12.8k
String case change methods should return intrinsic string manipulation types #44268
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
Comments
Related: type Char = 'a' | 'A';
let char: Char = 'a';
char = char.toUpperCase();
//^ Error: Type 'string' is not assignable to type 'Char'. String methods should be type |
I have a generated string union type definition like this: export type Align =
| 'LEFT'
| 'CENTER'
| 'RIGHT'
| 'DEFAULT'; I also have an existing object literal export containing CSS-in-JS classNames that correspond to export const textAlignClasses = {
left: css``,
center: css``,
right: css``,
/** Avoids TS7053 when using array notation */
default: undefined,
}; Unfortunately, I cannot currently use
I am current working around this limitation with a type assertion: const textAlign: Align = 'DEFAULT';
const textAlignClass = textAlignClasses[textAlign.toLowerCase() as Lowercase<Align>]; |
Hm, this doesn't seem possible without making the entire String interface generic... |
@Nixinova it's only those 4 methods (upper/lower plus "locale" versions of same) that would need to change. None of them take a function argument, so the function can't (usefully) be generic. My initial thought was, for non-primitive types, you can use I guess that means, the change I'm asking about might have to happen in the compiler rather than the type lib, which I recognize makes it less likely to happen. |
i tried extending the declare global {
//TS2428: All declarations of 'String' must have identical type parameters.
interface String<T extends string> {
toLowerCase(): Lowercase<T>
toUpperCase(): Uppercase<T>
}
}
const foo = 'FOO'.toLowerCase() //TS2339: Property 'toLowerCase' does not exist on type '"FOO"'. |
Just use a generic interface String {
toUpperCase<T extends string>(this: T): Uppercase<T>;
} |
Thanks @ajafff , I thought I'd tried that earlier without success, but plugging my example into Playground works perfectly. Does this mean a PR would be as simple as changing the signature of those 4 methods? |
Any updates on this? looks like someone started implementing but then quit in December |
This issue is "awaiting more feedback". This means we'd like to hear from more people who would be helped by this feature, understand their use cases, and possibly contrast with other proposals that might solve the problem in a simpler way (or solve many other problems at once). If this feature looks like a good fit for you, please use the 👍 reaction on the original post. Comments outlining different scenarios that would be benefited from the feature are also welcomed. |
If it helps to have a more detailed use case, I'm trying to exchange data between two systems, and I have an interface that describes the shape of the JSON returned / expected by their REST APIs. One uses lowercase keys, the other uses UPPERCASE, and I want the type checker to be happy with a case-change method, without having to add type assertions. If I do |
Thanks for the information, Ryan! My use case is very similar to the one provided by thw0rted |
Maybe it doesn't really constitute feedback, but there was a StackOverflow question about this: https://stackoverflow.com/questions/71646698/modified-template-literal-types/ |
Wanted to comment as you mentioned you wanted to hear if others find this useful. I was searching for a workaround for this exact scenario so yes it helps me a lot. We are eslint banning type assertions in our code base so would rather not have to do type assertions and augmenting built in types is not a great solution. Plus this is the expected behavior since this is how the function actually works. |
I have a question that seems related, but if other methods like The tighter the types the better. |
@tomerghelber-tm my ts-helpers package includes a const foo = trim(' foo ') // type is "foo" it would be cool if these were in the lib types, but i imagine performance is a concern with stuff like this |
This issue still persists, is there anything planned? |
It would be neat if trim worked too, though I assume there are weird edge cases in the spec for what counts as "whitespace". Is there an intrinsic type that already handles this? (If not, maybe there should be?) |
I'd love this too. My use case is that a third party library returns values in lowercase, and I'm uppercasing them to match against pre-existing object keys. Currently, code looks like: const result = getResult(); // returns a lowercase literal type string value
const lookupResult = LookupTable[result.toUpperCase() as Uppercase<typeof result>]; Just seems ugly |
#44268 (comment) this is a workaround that's better than manually casting the type |
Thanks for the tip. Still seems like this should be built in. |
Agreed - that's what this issue is about is making it a built in type definition |
@RyanCavanaugh Since this issue is "awaiting more feedback" as opposed to "help wanted", I guess that means that the TS team thought that updating the typings might not be a good idea for some reason? Can you spell out what that reason is? I'm really struggling to see the downside of this change. |
All features incur risk and maintenance cost, especially the ones you think won't |
While I understand the desire for caution, that seems like a non-answer to @ethanresnick's question |
All features start at minus 100; the default answer to any feature request is "no" in the absence of a strong case in favor of it. Looking into these features and exploring their potential downsides is itself a cost, as evidenced by the time I'm spending constructing this comment itself (both in terms of typing it and the research I did to create it). We can't ship every feature all at once without creating chaos, so we are really only able to spend finite engineer time and risk on things that provide really clear upside, which I don't think this proposal does. The presumed declaration we'd add is interface String {
toUpperCase<T extends string>(this: T): Uppercase<T>;
toLowerCase<T extends string>(this: T): Lowercase<T>;
} Now, notably, you can already do this in userland today by writing this exact snippet anywhere in the global scope. So in that sense, the feature is there and available for anyone who wants to use it; no one is blocked. Should this then be the default behavior by removing the other overloads? What would this break? Here's an example you could imagine (using interface String {
toUpperCase2<T extends string>(this: T): Uppercase<T>;
toLowerCase2<T extends string>(this: T): Lowercase<T>;
}
const m = "hello";
const arr = [m.toLowerCase2()];
arr.push("world"); // <- now an error |
@RyanCavanaugh I agree that the value of the proposed change here is relatively small, in that this it's serving a fairly rare use case and, as you pointed out, 'fixing' this is possible in userland already. So I appreciate you taking the time to construct the example. My read of that example, though, is that it might be highlighting a different TS issue. It shows that a type produced by const m = "hello";
const arr = [m]; // m has a literal type, but arr is still inferred as string[]
arr.push("world"); I think a lot of people would find it quite weird/unintuitive that If that different treatment for |
I think the root cause is that |
@RyanCavanaugh Makes sense. I’ll open an issue about having these intrinsic types preserve the widening status of their input, to at least start a discussion about that — though I’ll also have to try to think of an example first. If the intrinsic types were changed like that, would you support changing the toLowerCase/toUpperCase definitions per this thread? Even if the signatures proposed here are only better in rare cases, it seems like those cases are common enough (given the thumbs up on this issue) that these revised signatures ought to be the default, if we can’t come up with another example of them causing any problems |
Another example use case is in #50548 (comment) where even what is supposed to be a highly simplified example still requires a casting function: //Bonus issue(#44268): the cast in the return statement of the next line should be automatic & unnecessary:
const toUpperCaseTyped = function<S extends string>(strIn: S) {return strIn.toUpperCase() as Uppercase<S>;}; |
I'll add some straightforward examples for thought. I came across some of these while I was trying to ensure that I've done the necessary If we have this function function greet(name: Uppercase<string>): void {
console.log(name)
} Here's how the actual behaviour compares to what, in my opinion, would be natural to expect:
The most counter-intuitive things are that |
casting casting should only cause an error if the types don't overlap though (ie. casting |
@DetachHead Case transformations aren't very simple, but this issue seems like it should be quite readily solvable. |
Any news on this? |
Hey, how many thumbs up would you need to consider solving this issue? |
There is no specific number; all open feature suggestions are considered holistically. Given that you're already free to opt into this in your project today (see prior comments), it's not a particularly high-priority thing. |
I would like to say that even if it might a breaking change, it should not stop typescript team from adding it. I mean - we all know that these methods will return lower case value, right? So real JS applications will not break |
lib Update Request
Configuration Check
My compilation target is
ES2018
and my lib is["ES2016", "dom"]
.Missing / IncorrectImprecise DefinitionsAt least
toUpperCase
andtoLowerCase
methods onString
, maybe/probably also theLocale
versions?Sample Code
Documentation Link
https://tc39.es/ecma262/#sec-string.prototype.tolowercase
Note
I'm not sure about the
Locale
versions of these methods because I don't know what algorithm the "intrinsic"Uppercase<T>
/Lowercase<T>
helper types are required to follow. (Do we need separateLocaleUppercase
/LocaleLowercase
helpers?)Also worth mentioning: I think what I'm looking for is a return type of e.g.
Uppercase<this>
, which shouldn't have an impact on non-literal string types becauseUppercase<string>
is juststring
.The text was updated successfully, but these errors were encountered: