Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5671,7 +5671,7 @@ internal static IConversionData FigureConversion(Type fromType, Type toType)
converter = FigureCastConversion(fromType, toType, ref rank);
if (converter == null)
{
if (typeof(IConvertible).IsAssignableFrom(fromType))
if (fromType != typeof(InternalPSObject) && typeof(IConvertible).IsAssignableFrom(fromType))
{
if (LanguagePrimitives.IsNumeric(GetTypeCode(fromType)) && !fromType.IsEnum)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.Management.Automation/engine/MshObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace System.Management.Automation
/// </remarks>
[TypeDescriptionProvider(typeof(PSObjectTypeDescriptionProvider))]
[Serializable]
public class PSObject : IFormattable, IComparable, ISerializable, IDynamicMetaObjectProvider
public partial class PSObject : IFormattable, IComparable, ISerializable, IDynamicMetaObjectProvider
{
#region constructors

Expand Down
106 changes: 106 additions & 0 deletions src/System.Management.Automation/engine/MshObjectIConvertible.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System.Diagnostics.CodeAnalysis;

namespace System.Management.Automation
{
/// <summary>
/// <see cref="PSObject"/> implements <see cref="System.IConvertible"/> interface for its base object.
/// </summary>
/// <remarks>
/// If <see cref="PSObject"/> wraps a value type implementing <see cref="System.IConvertible"/>
/// it provides that functionality too.
/// </remarks>
/// <exception cref="System.InvalidCastException">The base object's implementation cannot convert and throws.</exception>
public partial class PSObject : IConvertible
{
/// <summary>
/// Returns the type code of the base object wrapped by the <see cref="PSObject"/>.
/// </summary>
/// <remarks>
/// If the base object is <see cref="PSObject"/> the method returns <see cref="TypeCode.Object"/>
/// i.e. it does not implement <see cref="System.IConvertible"/>.
/// Otherwise, the result is the type code of the base object,
/// as determined by the base object's implementation of <see cref="System.IConvertible"/>.
/// </remarks>
/// <returns>Returns <see cref="System.TypeCode"/> of the PSObject.</returns>
public TypeCode GetTypeCode()
{
object? obj = PSObject.Base(this);

// Take into account PSObject and all derived classes like InternalPSObject.
return obj is null || typeof(PSObject).IsAssignableFrom(obj.GetType()) ? TypeCode.Object : Convert.GetTypeCode(obj);
}

/// <inheritdoc/>
public bool ToBoolean(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToBoolean(provider);

/// <inheritdoc/>
public char ToChar(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToChar(provider);

/// <inheritdoc/>
public sbyte ToSByte(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToSByte(provider);

/// <inheritdoc/>
public byte ToByte(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToByte(provider);

/// <inheritdoc/>
public short ToInt16(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt16(provider);

/// <inheritdoc/>
public ushort ToUInt16(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt16(provider);

/// <inheritdoc/>
public int ToInt32(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt32(provider);

/// <inheritdoc/>
public uint ToUInt32(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt32(provider);

/// <inheritdoc/>
public long ToInt64(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt64(provider);

/// <inheritdoc/>
public ulong ToUInt64(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt64(provider);

/// <inheritdoc/>
public float ToSingle(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToSingle(provider);

/// <inheritdoc/>
public double ToDouble(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDouble(provider);

/// <inheritdoc/>
public decimal ToDecimal(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDecimal(provider);

/// <inheritdoc/>
public DateTime ToDateTime(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDateTime(provider);

/// <inheritdoc/>
public string ToString(IFormatProvider? provider) => this.ToString(format: null, provider);

/// <inheritdoc/>
public object ToType(Type conversionType, IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToType(conversionType, provider);

private static IConvertible GetIConvertibleOrThrow(PSObject pso)
{
object obj = PSObject.Base(pso);
if (obj is PSObject || obj is not IConvertible value)
{
ThrowInvalidCastException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this method is only used once here, would it be better to simply have this block throw the exception and not have the workaround?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw blocks inlining. .Net suggests long term patterns like ArgumentNullException.ThrowIf(). We can benefit from this too.


// We have to use the workaround
// because of C# limitations (it doesn't know that ThrowInvalidCastException() is never returned).
// DoesNotReturn don't address the scenario.
// See https://github.com/dotnet/runtime/issues/79647#issuecomment-1351370392
return null!;
}

return value;
}

[DoesNotReturn]
private static void ThrowInvalidCastException() => throw new InvalidCastException();
}
}
31 changes: 31 additions & 0 deletions test/powershell/Language/Scripting/Scripting.Followup.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,35 @@ public class NullStringTest {
$result = & $powershell -noprofile -c '[System.Text.Encoding]::GetEncoding("IBM437").WebName'
$result | Should -BeExactly "ibm437"
}

It 'Can set DataRow with PSObject through adapter and indexer' {
$dataTable = [Data.DataTable]::new()
$null = $dataTable.Columns.Add('Date', [DateTime])
$row = $dataTable.Rows.Add((Get-Date))

$date1 = Get-Date
$row.Date = $date1
$row.Date.Ticks | Should -Be $date1.Ticks

$date2 = Get-Date
$row['Date'] = $date2
$row.Date.Ticks | Should -Be $date2.Ticks
}

It 'Can set DataRowView with PSObject through adapter and indexer' {
$dataTable = [Data.DataTable]::new()
$null = $dataTable.Columns.Add('Date', [DateTime])
$dataTable.Rows.Add((Get-Date))

$dataView = [System.Data.DataView]::new($dataTable)
$rowView = $dataView[0]

$date1 = Get-Date
$rowView.Date = $date1
$rowView.Date.Ticks | Should -Be $date1.Ticks

$date2 = Get-Date
$rowView['Date'] = $date2
$rowView.Date.Ticks | Should -Be $date2.Ticks
}
}
214 changes: 214 additions & 0 deletions test/xUnit/csharp/test_PSObjectIConvertible.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Management.Automation;
using Xunit;

namespace PSTests.Parallel
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:UseBuiltInTypeAlias", Justification = "Intentionally not using built-in alias.")]
public static class PSObjectIConvertibleTests
{
[Fact]
public static void TestBool()
{
var value = (object)true;
var wrappedValue = new PSObject(true);
Assert.Equal(value, wrappedValue.ToBoolean(provider: null));
Assert.Equal(typeof(bool).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestChar()
{
var value = (object)'K';
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToChar(provider: null));
Assert.Equal(typeof(char).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToSByte()
{
var value = (object)(SByte)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToSByte(provider: null));
Assert.Equal(typeof(SByte).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToByte()
{
var value = (object)(Byte)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToByte(provider: null));
Assert.Equal(typeof(Byte).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToInt16()
{
var value = (object)(Int16)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToInt16(provider: null));
Assert.Equal(typeof(Int16).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToUInt16()
{
var value = (object)(UInt16)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToUInt16(provider: null));
Assert.Equal(typeof(UInt16).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToInt32()
{
var value = (object)(Int32)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToInt32(provider: null));
Assert.Equal(typeof(Int32).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToUInt32()
{
var value = (object)(UInt32)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToUInt32(provider: null));
Assert.Equal(typeof(UInt32).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToInt64()
{
var value = (object)(Int64)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToInt64(provider: null));
Assert.Equal(typeof(Int64).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToUInt64()
{
var value = (object)(UInt64)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToUInt64(provider: null));
Assert.Equal(typeof(UInt64).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToSingle()
{
var value = (object)(Single)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToSingle(provider: null));
Assert.Equal(typeof(Single).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToDouble()
{
var value = (object)(Double)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToDouble(provider: null));
Assert.Equal(typeof(Double).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToDecimal()
{
var value = (object)(Decimal)1;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToDecimal(provider: null));
Assert.Equal(typeof(Decimal).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToDateTime()
{
var value = (object)DateTime.UtcNow;
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToDateTime(provider: null));
Assert.Equal(typeof(DateTime).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToString()
{
var value = (object)"1";
var wrappedValue = new PSObject(value);
Assert.Equal(value, wrappedValue.ToString(provider: null));
Assert.Equal(typeof(String).GetTypeCode(), wrappedValue.GetTypeCode());
}

[Fact]
public static void TestToType()
{
var value = (object)1;
var wrappedValue = new PSObject(value);
Assert.Equal(typeof(bool), wrappedValue.ToType(typeof(bool), provider: null).GetType());
Assert.Equal(typeof(char), wrappedValue.ToType(typeof(char), provider: null).GetType());
Assert.Equal(typeof(SByte), wrappedValue.ToType(typeof(SByte), provider: null).GetType());
Assert.Equal(typeof(Byte), wrappedValue.ToType(typeof(Byte), provider: null).GetType());
Assert.Equal(typeof(Int16), wrappedValue.ToType(typeof(Int16), provider: null).GetType());
Assert.Equal(typeof(UInt16), wrappedValue.ToType(typeof(UInt16), provider: null).GetType());
Assert.Equal(typeof(Int32), wrappedValue.ToType(typeof(Int32), provider: null).GetType());
Assert.Equal(typeof(UInt32), wrappedValue.ToType(typeof(UInt32), provider: null).GetType());
Assert.Equal(typeof(Int64), wrappedValue.ToType(typeof(Int64), provider: null).GetType());
Assert.Equal(typeof(UInt64), wrappedValue.ToType(typeof(UInt64), provider: null).GetType());
Assert.Equal(typeof(Single), wrappedValue.ToType(typeof(Single), provider: null).GetType());
Assert.Equal(typeof(Double), wrappedValue.ToType(typeof(Double), provider: null).GetType());
Assert.Equal(typeof(Decimal), wrappedValue.ToType(typeof(Decimal), provider: null).GetType());
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(DateTime), provider: null));
Assert.Equal(typeof(String), wrappedValue.ToType(typeof(String), provider: null).GetType());
}

[Fact]
public static void TestNoLoopAndStackOverflow()
{
// PSObject.Base(wrappedValue) returns PSObject.
// It is infinite loop.
// so PSObject IConvertible implementation should throw
// with InvalidCastException instead of StackOverflowException.
var wrappedValue = new PSObject();
Assert.Throws<InvalidCastException>(() => wrappedValue.ToBoolean(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToChar(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToSByte(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToByte(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt16(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt16(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt32(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt32(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt64(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt64(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToSingle(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToDouble(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToDecimal(provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToDateTime(provider: null));

Assert.Equal(string.Empty, wrappedValue.ToString(provider: null));

Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(bool), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(char), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(SByte), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Byte), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int16), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt16), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int32), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt32), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int64), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt64), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Single), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Double), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Decimal), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(DateTime), provider: null));
Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(String), provider: null));

Assert.Equal(TypeCode.Object, wrappedValue.GetTypeCode());
}
}
}