Repository navigation
Allow convertion from string consisting of single code point to System.Text.Rune #27596
Copy link
Copy link
Open
Labels
Issue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugNeeds-TriageThe issue is new and needs to be triaged by a work group.The issue is new and needs to be triaged by a work group.WG-Enginecore PowerShell engine, interpreter, and runtimecore PowerShell engine, interpreter, and runtimeWG-NeedsReviewNeeds a review by the labeled Working GroupNeeds a review by the labeled Working Group
Description
Summary of the new feature / enhancement
PS> [char]"A"
A
PS> [char]"😀"
InvalidArgument: Cannot convert value "😀" to type "System.Char". Error: "String must be exactly one character long."
PS> [Text.Rune]"A"
InvalidArgument: Cannot convert the "A" value of type "System.String" to type "System.Text.Rune".
System.Text.Rune can represent supplementary characters outside BMP like "😀" but it can't be directly converted by string like char. Instead, you're required to take the following complex and inefficient way:
PS> "😀".EnumerateRunes()[0]
IsAscii : False
IsBmp : False
Plane : 1
Utf16SequenceLength : 2
Utf8SequenceLength : 4
Value : 128512
Proposed technical implementation details (optional)
Permits only strings that consist of a single BMP Unicode scalar value (not a surrogate code unit) or a single surrogate pair (a high surrogate code unit + a low surrogate code unit).
PS> [Text.Rune]"AA"
InvalidArgument: Cannot convert value "AA" to type "System.Text.Rune". Error: "String must be exactly one character long."
PS> [Text.Rune]"👨🏼💻"
InvalidArgument: Cannot convert value "👨🏼💻" to type "System.Text.Rune". Error: "String must be exactly one character long."
PS> [Text.Rune]"`u{d800}"
InvalidArgument: Cannot convert value "�" to type "System.Text.Rune". Error: "Surrogate code unit cannot be converted to Rune."
Use the constructor of Rune to create its instance from the code point:
PS> [Text.Rune]::new(0x41)
IsAscii : True
IsBmp : True
Plane : 0
Utf16SequenceLength : 1
Utf8SequenceLength : 1
Value : 65
PS> [Text.Rune]::new(0x1F600)
IsAscii : False
IsBmp : False
Plane : 1
Utf16SequenceLength : 2
Utf8SequenceLength : 4
Value : 128512
PS> [Text.Rune]::new(0xd800)
MethodInvocationException: Exception calling ".ctor" with "1" argument(s): "Specified argument was out of the range of valid values. (Parameter 'value')"
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Issue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugNeeds-TriageThe issue is new and needs to be triaged by a work group.The issue is new and needs to be triaged by a work group.WG-Enginecore PowerShell engine, interpreter, and runtimecore PowerShell engine, interpreter, and runtimeWG-NeedsReviewNeeds a review by the labeled Working GroupNeeds a review by the labeled Working Group
Activity
mklement0 commentedon Jun 14, 2026
rhubarb-geek-nz:
The point of this issue is to propose adding a new capability to PowerShell, as a quality-of-life improvement.
As such, it isn't sufficient to show how the suggested capability can - with nontrivial effort - implemented using the current capabilities, as you did.
*
@("😀A".EnumerateRunes())[0]* That is, enumeration must be forced and the results collected in an array via
@(...)in order to apply indexing.If your argument is that existing solutions are simple enough / the use case is too exotic, ... so as not to warrant adding a new capability to PowerShell, please argue that point.
tats-u commentedon Jun 15, 2026
Even JavaScript can do something similar with the spread operator, so it's only natural.
It's the order of precedence between operators. If you want to make it explicit, you should enclose some parts in parentheses.
mklement0 commentedon Jun 15, 2026
To summarize:
The suggestion is to add support for:
[System.Text.Rune]to construct an instance of the latter, so that, e.g.,[System.Text.Rune] '😀'would yield the following
[System.Text.Rune]instance (Unicode code point U+1f600 == decimal value 128512; making the for-display formatting for[System.Text.Rune]more useful to the human observer is the subject of Prettily displaySystem.Text.Runeinstance #27595):[System.Text.Rune[]], so that, e.g.[System.Text.Rune[]] '😀!'would yield:
The above mirrors the established behavior of the conceptually closely related
[char](System.Char) type (e.g.[char] '!'or[char[]] 'foo'), and therefore sounds like a useful addition that makes dealing with non-BMP (Basic Multilingual Plane) Unicode characters easier.The only small caveat is that while the
[char]and[char[]]casts rely on standard PowerShell casting techniques - namely[char]having a staticParse(string s)method and[string]implementing theSystem.Collections.Generic.IEnumerable`1[System.Char]interface - the absence of analogousRune-related methods / interfaces prevents the proposed casts from working out of the box.As such,
[System.Text.Rune]and[System.Text.Rune[]]would have to be special-cased, though to me that still seems worth doing, given the analogous functioning of the conceptually closely related[char]and[char[]]casts (I don't think users would think twice about such implementation-related special-casing).tats-u commentedon Jun 16, 2026
You are required to combine
<string instance>.EnumerateRunes()and<IEnumerate<Rune> instance>.toArray().However, it's a shame that
Rune.(Try)Parse(string)doesn't exist. We will need to suggest an API in dotnet/runtime.mklement0 commentedon Jun 16, 2026
Yes, at the .NET level,
Rune.(Try)Parse(string)should arguably exist for symmetry withSystem.Char. Additionally,System.Stringshould also implementSystem.Collections.Generic.IEnumerable`1[System.Text.Rune]. If these existed, the functionality of this proposal would automatically become available. I encourage you to open an issue at https://github.com/microsoft/dotnet/issuesGeeLaw commentedon Sep 21, 2026
mklement0 commentedon Sep 21, 2026
Ji Luo (@GeeLaw):
What makes you think that?
GeeLaw commentedon Sep 22, 2026
GeeLaw commentedon Oct 15, 2026
mklement0 commentedon Oct 16, 2026
Ji Luo (@GeeLaw) (refocusing on the core issue first):
[Text.Rune] '😀'should work in .NET 11+ (underlying the current PowerShell v7.7 preview versions), sinceSystem.Text.Runenow implementsIParsable<Rune>; however, due to a design limitation in PowerShell it isn't utilized because it is an explicit interface implementation; In from-string conversions, also honorIParsable<T>interface implementations, not just public staticParsemethods #28222 proposes that this limitation be lifted.While
[Text.Rune[]] '😀!'would work ifSystem.StringsupportedSystem.Collections.Generic.IEnumerable`1[System.Text.Rune], I now agree that this is not an option (see below). That said, I still think it is worth special-casing support for[Text.Rune[]] '😀!', as a helpful convenience that parallels[char[]]support (and whose special-casing few people would notice, if I were to guess).(Tangent alert; re types implementing multiple
IEnumerable<T>interfaces)Details
Yes, adding a second
IEnumerable<T>interface later to an existing type can break existing code, and to me usage in the context of LINQ is the most compelling example (it forces explicit selection of the interface of interest).In fact, a real-world example is
System.Text.RegularExpressions.GroupCollection, which now implements bothIEnumerable<Group>andIEnumerable<KeyValuePair<String, Group>>, and the introduction of the second enumerable interface broke existing code (but, from what I understand, it was decided not to revert the change, as it would have broken code taking advantage of the new capabilities).This is somewhat regrettable, as there could conceivably be a way for a type to designate a default
IEmumerable<T>interface.De facto such a mechanism exists in the context of
foreachloops, though that may be more of a historical accident than by deliberate design:foreachlooks for a publicGetEnumeratormethod first, which means that it will implicitly select an implicitIEmumerable<T>implementation. That is, a type can implement what it wants to be the default enumerable interface as the (by definition only) implicitIEnumerable<T>implementation, andforeachwill honor that - but, as noted, sadly, LINQ will not.My knowledge isn't deep enough to assess whether bringing the same default-interface concept to LINQ would even be possible, both conceptually and technically.