Skip to content

Allow convertion from string consisting of single code point to System.Text.Rune #27596

Open

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')"

Activity

  1. mklement0 commented on Jun 14, 2026

    @mklement0
    Contributor

    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.

      • The fact that nontrivial effort is required is most likely what motivated the proposal to begin with.
      • The ad hoc workaround suggested in the initial post is probably the simplest one, though it needs to be modified:
        * @("😀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.

  2. tats-u commented on Jun 15, 2026

    @tats-u
    Author

    would we also expect it to convert a string to an array of runes?

    Even JavaScript can do something similar with the spread operator, so it's only natural.

    Would the range be in terms of characters or runes?

    It's the order of precedence between operators. If you want to make it explicit, you should enclose some parts in parentheses.

  3. mklement0 commented on Jun 15, 2026

    @mklement0
    Contributor

    To summarize:

    The suggestion is to add support for:

    • casting a string representing a single rune (scalar Unicode character) to [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 display System.Text.Rune instance #27595):
    IsAscii             : False
    IsBmp               : False
    Plane               : 1
    Utf16SequenceLength : 2
    Utf8SequenceLength  : 4
    Value               : 128512
    
    • casting an arbitrary string composed of zero or more runes to an array using [System.Text.Rune[]], so that, e.g.
      [System.Text.Rune[]] '😀!'
      would yield:
    IsAscii             : False
    IsBmp               : False
    Plane               : 1
    Utf16SequenceLength : 2
    Utf8SequenceLength  : 4
    Value               : 128512
    
    IsAscii             : True
    IsBmp               : True
    Plane               : 0
    Utf16SequenceLength : 1
    Utf8SequenceLength  : 1
    Value               : 33
    

    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 static Parse(string s) method and [string] implementing the System.Collections.Generic.IEnumerable`1[System.Char] interface - the absence of analogous Rune-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).

  4. tats-u commented on Jun 16, 2026

    @tats-u
    Author

    the absence of analogous Rune-related methods / interfaces prevents the proposed casts from working out of the box.

    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.

  5. mklement0 commented on Jun 16, 2026

    @mklement0
    Contributor

    Yes, at the .NET level, Rune.(Try)Parse(string) should arguably exist for symmetry with System.Char. Additionally, System.String should also implement System.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/issues

    GitHub
    This repo is the official home of .NET on GitHub. It's a great starting point to find many .NET OSS projects from Microsoft and the community, including many that are part of the .NET Foundatio...
  6. GeeLaw commented on Sep 21, 2026

    @GeeLaw
  7. mklement0 commented on Sep 21, 2026

    @mklement0
    Contributor

    Ji Luo (@GeeLaw):

    I consider there to be an invariant that, if U implements IEnumerable<T>, then U's IEnumerable implementation should enumerate the same sequence as IEnumerable<T>

    What makes you think that?

  8. GeeLaw commented on Sep 22, 2026

    @GeeLaw
  9. GeeLaw commented on Oct 15, 2026

    @GeeLaw
  10. mklement0 commented on Oct 16, 2026

    @mklement0
    Contributor

    Ji Luo (@GeeLaw) (refocusing on the core issue first):

    • [Text.Rune] '😀' should work in .NET 11+ (underlying the current PowerShell v7.7 preview versions), since System.Text.Rune now implements IParsable<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 honor IParsable<T> interface implementations, not just public static Parse methods #28222 proposes that this limitation be lifted.

    • While [Text.Rune[]] '😀!' would work if System.String supported System.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 both IEnumerable<Group> and IEnumerable<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 foreach loops, though that may be more of a historical accident than by deliberate design: foreach looks for a public GetEnumerator method first, which means that it will implicitly select an implicit IEmumerable<T> implementation. That is, a type can implement what it wants to be the default enumerable interface as the (by definition only) implicit IEnumerable<T> implementation, and foreach will 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugNeeds-TriageThe issue is new and needs to be triaged by a work group.WG-Enginecore PowerShell engine, interpreter, and runtimeWG-NeedsReviewNeeds a review by the labeled Working Group

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions