3

Why does an implicit conversion to [byte] work, but when replacing byte by bool it no longer works?

I. e. the following works...

Add-Type -TypeDefinition @'
public readonly struct MyByte
{
    private readonly byte value;

    public MyByte( byte b ) => this.value = b;

    public static implicit operator byte( MyByte b ) => b.value;
    public static explicit operator MyByte( byte b ) => new MyByte( b );

    public override string ToString() => $"{value}";
}
'@

[byte] $d = [MyByte]::new( 1 )    # OK

...while this very similar code does not:

Add-Type -TypeDefinition @'
public readonly struct MyBool
{
    private readonly bool value;

    public MyBool( bool b ) => this.value = b;

    public static implicit operator bool( MyBool b ) => b.value;
    public static explicit operator MyBool( bool b ) => new MyBool( b );

    public override string ToString() => $"{value}";
}
'@

[bool] $b = [MyBool]::new( $true )    # Error

This produces the following error:

Cannot convert value "MyBool" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.

Note that in C# the implicit conversion to bool works as expected:

public class MyBoolTest {
    public static void Test() {
        bool b = new MyBool( true );    // OK
    }
}

So this seems to be a PowerShell issue only.

(PSVersion: 7.2.2)

7
  • 2
    Odd that $b = [bool][MyBool]::new( $true ) works but [bool] $b = [MyBool]::new( $true ) doesn't Commented Mar 26, 2022 at 19:43
  • 1
    @SantiagoSquarzon The difference is that the first one is an explicit conversion. An implicit conversion operator can be used for both, but PoSh does not seem to recognize it for the implicit case. Commented Mar 26, 2022 at 19:49
  • 2
    @SantiagoSquarzon [bool][MyBool]::new( $false ) -> surprisingly prints True! Commented Mar 26, 2022 at 20:07
  • 2
    @SantiagoSquarzon As always, @mklement0 seems to have the answer. In short: conversion to bool is entirely handled by PowerShell and ignores any implicit and explicit cast operators (further detailed in comments of LanguagePrimitives.cs). Commented Mar 26, 2022 at 20:14
  • 1
    btw, $a = [MyBool]::new(1); $a | gm -force now $a no longer has a value LOL Commented Mar 26, 2022 at 20:14

1 Answer 1

2

You've done most of the discovery yourself already, assisted by Santiago Squarzon, but let me try to summarize:

You're seeing two separate problematic PowerShell behaviors:

  • Problematic behavior A: PowerShell has its own, built in to-Boolean conversion logic, which, unfortunately, does not honor implicit or explicit .NET conversion operators.

    • The bottom section of this answer summarizes the rules of this built-in logic, which explains why it considers any instance of your [MyBool] type - even [MyBool]::new($false) - $true, unfortunately.

    • Only in operations where an instance isn't coerced to a Boolean first are the conversion operators honored, which for most operators means using the instance on the LHS:

      [MyBool]::new($false) -eq $false # -> $true
      
      [MyBool]::new($false), 'other' -contains $false # -> $true
      
      # With -in, it is the *RHS* that matters 
      $false -in [MyBool]::new($false), 'other' # -> $true
      
    • By contrast, if you force a Boolean context - either by using a Boolean on the (typically) LHS or with implicit to-Boolean coercion - PowerShell's built-in logic - which doesn't honor conversion operators - kicks in:

      $false -eq [MyBool]::new($false) # -> !! $false
      
      $false, 'other' -contains [MyBool]::new($false) # -> !! $false
      
      # With -in, it is the *RHS* that matters 
      [MyBool]::new($false) -in $false, 'other' # -> !! $false
      
      # Most insidiously, with *implicit* coercion.
      if ([MyBool]::new($false)) { 'what?' } # -> !! 'what?'
      
    • This problematic behavior, discussed in GitHub issue #24706, equally affects implementations of the true / false operators.

  • Problematic behavior B: When you type-constrain a variable with [bool], i.e. when you place the type literal to the left of the variable being assigned (e.g, [bool] $b = ..., as opposed to $b = [bool] (...),[1] the rules for binding a [bool] parameter - unexpectedly and inappropriately - kick in, which - unlike the any-type-accepted built-in to-Boolean conversion - are quite restrictive, as the error message indicates.

    • That is, only $true, $false and numbers (with zero mapping to $false and any nonzero value to $true) may be passed to a parameter typed [bool].

      • Note that [bool] parameters themselves are rare, because Boolean logic is PowerShell-idiomatically expressed with a [switch] parameter instead, which, when it is (non-typically) given an explicit argument, is even more restrictive and accepts $true and $false only.
    • This problematic behavior - inappropriately applying parameter logic to (non-parameter) variables - is the subject of GitHub issue #10426.


[1] The difference between the two is that type-constraining - [bool] $b = ... - effectively locks in the data type of variable $b, so that latter attempts to assign new values are coerced to the same type. By contrast, $b = [bool] (...) merely applies an ad hoc cast to force a conversion, without preventing later assignments from assigning values with different data types.

Sign up to request clarification or add additional context in comments.

7 Comments

@Santiago, that behavior is indeed very curious. Note that it isn't the value that is stripped, but that the for-display formatting suddenly stops working (prints an empty line instead of the value). You'll see that the value is still there with "$a". I encourage you to either ask a new question here, or to create an issue on GitHub.
@Santiago: yes, unquestionably.
Ok well, this same behavior will occur in a PS Class with a hidden property too. And it's not only gm breaking the for-display formatting, select * too
For those interested: Santiago posted a follow-up question here.
Submitted GitHub Issue #17071, thanks for the correction on the for-display formatting :) I linked your comment so that this Q&A is visible, if you don't mind please don't delete the comment
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.