From 85086ea11719d03e9e6a66bf934ecd7732c4fb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Fri, 18 Apr 2025 02:40:15 +0100 Subject: [PATCH 01/17] Update core library to add support for generics (#242) --- .runsettings | 5 +- Tests/NFUnitTestGC/TestGC.cs | 36 ++++++-- Tests/NFUnitTestSystemLib/UnitTestGCTest.cs | 85 ++++++++----------- .../UnitTestInitLocalTests.cs | 27 +++++- .../UnitTestReflectionTypeTest.cs | 72 ++++++++++------ .../UnitTestLauncher/UnitTestLauncher.nfproj | 2 +- azure-pipelines.yml | 3 + nanoFramework.CoreLibrary.nuspec | 7 +- .../System/AssemblyInfo.cs | 2 +- nanoFramework.CoreLibrary/System/GC.cs | 70 +++++++++++---- nanoFramework.CoreLibrary/System/Guid.cs | 5 +- nanoFramework.TestFramework | 2 +- version.json | 3 +- 13 files changed, 205 insertions(+), 114 deletions(-) diff --git a/.runsettings b/.runsettings index 3d17b679..536c73fb 100644 --- a/.runsettings +++ b/.runsettings @@ -1,4 +1,4 @@ - + @@ -12,5 +12,6 @@ Verbose False --forcegc + True - \ No newline at end of file + diff --git a/Tests/NFUnitTestGC/TestGC.cs b/Tests/NFUnitTestGC/TestGC.cs index 0bec97a8..bf52e121 100644 --- a/Tests/NFUnitTestGC/TestGC.cs +++ b/Tests/NFUnitTestGC/TestGC.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using nanoFramework.TestFramework; namespace NFUnitTestGC @@ -15,8 +16,6 @@ public void TestGCStress() int maxArraySize = 1024 * 32; object[] arrays = new object[600]; - // Starting TestGCStress - for (int loop = 0; loop < 100; loop++) { OutputHelper.WriteLine($"Running iteration {loop}"); @@ -24,7 +23,7 @@ public void TestGCStress() for (int i = 0; i < arrays.Length - 1;) { OutputHelper.WriteLine($"Alloc array of {maxArraySize} bytes @ pos {i}"); - arrays[i++] = new byte[maxArraySize]; ; + arrays[i++] = new byte[maxArraySize]; OutputHelper.WriteLine($"Alloc array of 64 bytes @ pos {i}"); arrays[i++] = new byte[64]; @@ -37,8 +36,35 @@ public void TestGCStress() arrays[i] = null; } } + } + + [TestMethod] + public void TestGetTotalMemory() + { + // create several objects + object[] objects = new object[100]; + + for (int i = 0; i < objects.Length; i++) + { + objects[i] = new object(); + } + + // get total memory + long totalMemory = GC.GetTotalMemory(false); + OutputHelper.WriteLine($"Total memory: {totalMemory} bytes"); + + // release objects + for (int i = 0; i < objects.Length; i++) + { + objects[i] = null; + } + + // get total memory, forcing full collection + long totalMemoryAfterCollection = GC.GetTotalMemory(true); + OutputHelper.WriteLine($"Total memory: {totalMemoryAfterCollection} bytes"); - // Completed TestGCStress + // check if memory was released + Assert.IsTrue(totalMemory > totalMemoryAfterCollection, "Memory was not released"); } } } diff --git a/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs b/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs index 05c73642..42a3e8f0 100644 --- a/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs +++ b/Tests/NFUnitTestSystemLib/UnitTestGCTest.cs @@ -9,6 +9,11 @@ namespace NFUnitTestSystemLib [TestClass] public class UnitTestGCTest { +#pragma warning disable S1215 // this is intended to test the GC +#pragma warning disable S1854 // this is intended to test the GC +#pragma warning disable S2696 // this is intended to test the GC +#pragma warning disable S3971 // this is intended to test the GC + internal class FinalizeObject { public static FinalizeObject m_currentInstance = null; @@ -54,17 +59,20 @@ public void SystemGC1_Test() /// 6. Verify that object has been collected /// /// - // Tests ReRegisterForFinalize - // Create a FinalizeObject. + + OutputHelper.WriteLine("Tests ReRegisterForFinalize"); + OutputHelper.WriteLine("Create a FinalizeObject."); + FinalizeObject mfo = new FinalizeObject(); m_hasFinalized1 = false; m_hasFinalized2 = false; // Release reference + OutputHelper.WriteLine("Release reference"); mfo = null; - // Allow GC - GC.WaitForPendingFinalizers(); + OutputHelper.WriteLine("Allow GC"); + GC.Collect(); int sleepTime = 1000; int slept = 0; @@ -85,10 +93,10 @@ public void SystemGC1_Test() // FinalizeObject.m_currentInstance field. Setting this value // to null and forcing another garbage collection will now // cause the object to Finalize permanently. - // Reregister and allow for GC - FinalizeObject.m_currentInstance = null; - GC.WaitForPendingFinalizers(); + OutputHelper.WriteLine("Reregister and allow for GC"); + FinalizeObject.m_currentInstance = null; + GC.Collect(); sleepTime = 1000; slept = 0; @@ -119,18 +127,19 @@ public void SystemGC2_Test() /// 6. Verify that object has not been collected /// /// - // Tests SuppressFinalize - // Create a FinalizeObject. + + OutputHelper.WriteLine("Tests SuppressFinalize"); + OutputHelper.WriteLine("Create a FinalizeObject"); FinalizeObject mfo = new FinalizeObject(); m_hasFinalized1 = false; m_hasFinalized2 = false; - // Releasing + OutputHelper.WriteLine("Releasing"); GC.SuppressFinalize(mfo); mfo = null; - // Allow GC - GC.WaitForPendingFinalizers(); + OutputHelper.WriteLine("Allow GC"); + GC.Collect(); int sleepTime = 1000; int slept = 0; @@ -138,7 +147,7 @@ public void SystemGC2_Test() while (!m_hasFinalized1 && slept < sleepTime) { // force GC run caused by memory allocation - var dummyArray = new byte[1024 * 1024 * 1]; + _ = new byte[1024 * 1024 * 1]; System.Threading.Thread.Sleep(10); slept += 10; @@ -161,59 +170,35 @@ public void SystemGC3_Test() /// /// - // Tests WaitForPendingFinalizers, dependant on test 1 - // will auto-fail if test 1 fails. OutputHelper.Write("Tests WaitForPendingFinalizers, dependant on test 1"); - OutputHelper.WriteLine("will auto-fail if test 1 fails."); + OutputHelper.WriteLine("will fail if test 1 fails."); - Assert.IsTrue(m_Test1Result); + Assert.IsTrue(m_Test1Result, "Can't run this test as SystemGC1_Test has failed."); - // Create a FinalizeObject. + OutputHelper.WriteLine("Create a FinalizeObject"); FinalizeObject mfo = new FinalizeObject(); m_hasFinalized1 = false; m_hasFinalized2 = false; - // Releasing + OutputHelper.WriteLine("Releasing"); mfo = null; - int sleepTime = 1000; - int slept = 0; - - while (!m_hasFinalized1 && slept < sleepTime) - { - // force GC run caused by memory allocation - var dummyArray = new byte[1024 * 1024 * 1]; - - System.Threading.Thread.Sleep(10); - slept += 10; - } - - OutputHelper.WriteLine($"GC took {slept}"); - - // Wait for GC + OutputHelper.WriteLine("Wait for GC"); + GC.Collect(); GC.WaitForPendingFinalizers(); - // Releasing again + OutputHelper.WriteLine("Releasing again"); FinalizeObject.m_currentInstance = null; - sleepTime = 1000; - slept = 0; - - while (!m_hasFinalized2 && slept < sleepTime) - { - // force GC run caused by memory allocation - var dummyArray = new byte[1024 * 1024 * 1]; - - System.Threading.Thread.Sleep(10); - slept += 10; - } - - OutputHelper.WriteLine($"GC took {slept}"); - - // Wait for GC + OutputHelper.WriteLine("Wait for GC"); + GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsTrue(m_hasFinalized2); } } +#pragma warning restore S1215 // "GC.Collect" should not be called +#pragma warning restore S1854 // Unused assignments should be removed +#pragma warning restore S2696 // Instance members should not write to "static" fields +#pragma warning restore S3971 // "GC.SuppressFinalize" should not be called } diff --git a/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs b/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs index fea53af7..93b37f48 100644 --- a/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs +++ b/Tests/NFUnitTestSystemLib/UnitTestInitLocalTests.cs @@ -49,6 +49,9 @@ public void SystemType1_GetType_Test() // ConstructorInfo) // NOTE: We add the reflection items to the ArrayList to assure that they can be properly // assigned to a object container (this used to lead to a access violation) + + OutputHelper.WriteLine("Testing Int32"); + Type type = typeof(int); list.Add(type); string name = ((Type)list[i]).Name; @@ -68,12 +71,16 @@ public void SystemType1_GetType_Test() //fRes &= name.ToLower() == "mscorlib"; //i++; + OutputHelper.WriteLine("Testing NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj"); + type = Type.GetType("NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj"); list.Add(type); name = ((Type)list[i]).Name; Assert.AreEqual(name, "TestObj"); i++; + OutputHelper.WriteLine("Testing IEmptyInterface"); + Type iface = type.GetInterfaces()[0]; list.Add(iface); name = ((Type)list[i]).Name; @@ -81,6 +88,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(iface.Name, "IEmptyInterface"); i++; + OutputHelper.WriteLine("Testing FieldInfo"); + FieldInfo fi = type.GetField("Field1"); list.Add(fi); name = ((FieldInfo)list[i]).Name; @@ -88,6 +97,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(fi.Name, "Field1"); i++; + OutputHelper.WriteLine("Testing MethodInfo"); + MethodInfo mi = type.GetMethod("Method1"); list.Add(mi); name = ((MethodInfo)list[i]).Name; @@ -95,6 +106,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(mi.Name, "Method1"); i++; + OutputHelper.WriteLine("Testing ConstructorInfo"); + ConstructorInfo ci = type.GetConstructor(new Type[] { }); list.Add(ci); name = ((ConstructorInfo)list[i]).Name; @@ -104,7 +117,10 @@ public void SystemType1_GetType_Test() // // Now test arrays of reflection types - // + // + + OutputHelper.WriteLine("Testing Array of Type"); + Type[] types = new Type[] { typeof(int), typeof(bool), Type.GetType("NFUnitTestSystemLib.UnitTestInitLocalTests+TestObj") }; list.Add(types[2]); name = ((Type)list[i]).Name; @@ -127,6 +143,8 @@ public void SystemType1_GetType_Test() //fRes &= asms[0].GetName().Name == "Microsoft.SPOT.Platform.Tests.Systemlib2"; //i++; + OutputHelper.WriteLine("Testing Array of FieldInfo"); + FieldInfo[] fis = new FieldInfo[] { types[2].GetField("Field1"), type.GetFields()[0] }; list.Add(fis[0]); name = ((FieldInfo)list[i]).Name; @@ -134,6 +152,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(fis[0].Name, "Field1"); i++; + OutputHelper.WriteLine("Testing Array of MethodInfo"); + MethodInfo[] mis = new MethodInfo[] { type.GetMethods()[2], types[2].GetMethod("Method2", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) }; list.Add(mis[1]); name = ((MethodInfo)list[i]).Name; @@ -141,6 +161,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(mis[1].Name, "Method2"); i++; + OutputHelper.WriteLine("Testing Array of ConstructorInfo"); + ConstructorInfo[] cis = new ConstructorInfo[] { types[2].GetConstructor(new Type[] { }), typeof(TestObj).GetConstructor(new Type[] { typeof(int) }) }; list.Add(cis[0]); name = ((ConstructorInfo)list[i]).Name; @@ -148,6 +170,8 @@ public void SystemType1_GetType_Test() Assert.AreEqual(cis[0].Name, ".ctor"); i++; + OutputHelper.WriteLine("Testing Array of System.Collections.ArrayList"); + Array ar = Array.CreateInstance(typeof(Type), 3); ((IList)ar)[0] = typeof(Type); ((IList)ar)[1] = Type.GetType("System.Collections.ArrayList"); @@ -157,7 +181,6 @@ public void SystemType1_GetType_Test() Assert.AreEqual(name, "ArrayList"); Assert.AreEqual(((Type)((IList)ar)[0]).Name, "Type"); Assert.AreEqual(((Type)ar.GetValue(1)).Name, "ArrayList"); - i++; list.Clear(); } diff --git a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs index 709f2122..471b394d 100644 --- a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs +++ b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs @@ -52,85 +52,103 @@ public void SystemReflectionType_RuntimeType_Test1() /// TestClass cls = new TestClass(); + OutputHelper.WriteLine("Testing Type members for a class type"); + // Test Type members for a class type Type t = cls.GetType(); Assembly asm = t.Assembly; list.Add(asm); - Assert.AreEqual(((Assembly)list[i]).GetName().Name, "NFUnitTest"); - Assert.AreEqual(asm.GetName().Name, "NFUnitTest"); - Assert.AreEqual(t.Name, "TestClass"); - Assert.AreEqual(t.FullName, "NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestClass"); + Assert.AreEqual("NFUnitTest", ((Assembly)list[i]).GetName().Name); + Assert.AreEqual("NFUnitTest", asm.GetName().Name); + Assert.AreEqual("TestClass", t.Name); + Assert.AreEqual("NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestClass", t.FullName); Assert.IsInstanceOfType(t.BaseType, typeof(object)); Assert.IsNull(t.GetElementType()); + OutputHelper.WriteLine("Testing methods of class type"); + MethodInfo[] mis = t.GetMethods(); - Assert.AreEqual(mis[0].Name, "Method1"); + Assert.AreEqual("Method1", mis[0].Name); mis = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); - Assert.AreEqual(mis[0].Name, "Method2"); + Assert.AreEqual("Method2", mis[0].Name); Assert.IsNotNull(t.GetMethod("Method1")); Assert.IsNotNull(t.GetMethod("Method2", BindingFlags.Instance | BindingFlags.NonPublic)); + OutputHelper.WriteLine("Testing fields of class type"); + FieldInfo[] fis = t.GetFields(); - Assert.AreEqual(fis[0].Name, "m_Field1"); + Assert.AreEqual("m_Field1", fis[0].Name); fis = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); - Assert.AreEqual(fis[0].Name, "m_Field2"); + Assert.AreEqual("m_Field2", fis[0].Name); Assert.IsNotNull(t.GetField("m_Field1")); Assert.IsNotNull(t.GetField("m_Field2", BindingFlags.NonPublic | BindingFlags.Instance)); Assert.IsNotNull(t.GetConstructor(new Type[] { })); + OutputHelper.WriteLine("Testing interfaces of class type"); + Type[] ifaces = t.GetInterfaces(); - Assert.AreEqual(ifaces.Length, 2); - Assert.AreEqual(ifaces[0].Name, "IInterface1"); - Assert.AreEqual(ifaces[1].Name, "IInterface2"); + Assert.AreEqual(2, ifaces.Length); + Assert.AreEqual("IInterface1", ifaces[0].Name); + Assert.AreEqual("IInterface2", ifaces[1].Name); Assert.IsTrue(t.IsSubclassOf(typeof(object))); i++; + OutputHelper.WriteLine("Testing Type members for a struct valuetype"); + // test Type members for a struct valuetype TestStruct str = new TestStruct(); t = str.GetType(); asm = t.Assembly; list.Add(asm); - Assert.AreEqual(((Assembly)list[i]).GetName().Name, "NFUnitTest"); - Assert.AreEqual(asm.GetName().Name, "NFUnitTest"); - Assert.AreEqual(t.Name, "TestStruct"); - Assert.AreEqual(t.FullName, "NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestStruct"); + Assert.AreEqual("NFUnitTest", ((Assembly)list[i]).GetName().Name); + Assert.AreEqual("NFUnitTest", asm.GetName().Name); + Assert.AreEqual("TestStruct", t.Name); + Assert.AreEqual("NFUnitTestSystemLib.UnitTestReflectionTypeTest+TestStruct", t.FullName); Assert.IsInstanceOfType(t.BaseType, typeof(ValueType)); - Assert.AreEqual(t.GetInterfaces().Length, 0); + Assert.AreEqual(0, t.GetInterfaces().Length); Assert.IsNull(t.GetElementType()); i++; + OutputHelper.WriteLine("Testing Type members for an Assembly reflection type"); + // test Type members for an Assembly reflection type //Assembly asmObj = typeof(TestClass).Assembly; //t = asmObj.GetType(); //asm = t.Assembly; //list.Add(asm); - //Assert.AreEqual(((Assembly)list[i]).GetName().Name, "mscorlib"); - //Assert.AreEqual(asm.GetName().Name, "mscorlib"); - //Assert.AreEqual(t.Name, "Assembly"); - //Assert.AreEqual(t.FullName, "System.Reflection.Assembly"); + //Assert.AreEqual("mscorlib", ((Assembly)list[i]).GetName().Name); + //Assert.AreEqual("mscorlib", asm.GetName().Name); + //Assert.AreEqual("Assembly", t.Name); + //Assert.AreEqual("System.Reflection.Assembly", t.FullName); //Assert.IsInstanceOfType(t.BaseType, typeof(Object)); - //Assert.AreEqual(t.GetInterfaces().Length, 0); + //Assert.AreEqual(0, t.GetInterfaces().Length); //Assert.IsNull(t.GetElementType()); + OutputHelper.WriteLine("Testing Type members for a MethodInfo reflection type"); + mis = typeof(TestClass).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); t = mis.GetType(); - Assert.AreEqual(t.Name, "RuntimeMethodInfo[]"); - Assert.AreEqual(t.FullName, "System.Reflection.RuntimeMethodInfo[]"); + Assert.AreEqual("RuntimeMethodInfo[]", t.Name); + Assert.AreEqual("System.Reflection.RuntimeMethodInfo[]", t.FullName); Assert.IsInstanceOfType(t.BaseType, typeof(Array)); Assert.IsTrue(t.GetInterfaces().Length > 0); - Assert.AreEqual(t.GetElementType().Name, "RuntimeMethodInfo"); + Assert.AreEqual("RuntimeMethodInfo", t.GetElementType().Name); + + OutputHelper.WriteLine("Testing Type members for a delegate"); // test Type members for a delegate Delegate del = new MyDelegate(MyDelegateImpl); t = del.GetType(); Assert.IsNotNull(t.DeclaringType); - Assert.AreEqual(t.Name, "MyDelegate"); + Assert.AreEqual("MyDelegate", t.Name); Assert.IsInstanceOfType(t.BaseType, typeof(MulticastDelegate)); + OutputHelper.WriteLine("Testing Type members for an enum"); + // test Type members for an enum TestEnum en = TestEnum.Item1; t = en.GetType(); - Assert.IsInstanceOfType(t.DeclaringType, typeof(UnitTestReflectionTypeTest)); + Assert.IsInstanceOfType(typeof(UnitTestReflectionTypeTest), t.DeclaringType); Assert.IsTrue(t.IsEnum); Assert.IsFalse(t.IsAbstract); Assert.IsFalse(t.IsClass); @@ -148,7 +166,7 @@ public void SystemReflectionType_SystemType_Test2() Assert.IsFalse(typeof(Array).IsValueType); Assert.IsTrue(typeof(TestStruct).IsValueType); Assert.IsTrue(typeof(Type).IsSubclassOf(typeof(MemberInfo))); - Assert.AreEqual(typeof(Type).GetInterfaces()[0].Name, "IReflect"); + Assert.AreEqual("IReflect", typeof(Type).GetInterfaces()[0].Name); Assert.IsTrue(typeof(MyDelegate).IsInstanceOfType(new MyDelegate(MyDelegateImpl))); // Get known type from assembly qualified type name Culture and PublicKeyToken are used by debugger to identify types diff --git a/Tests/UnitTestLauncher/UnitTestLauncher.nfproj b/Tests/UnitTestLauncher/UnitTestLauncher.nfproj index b03d676e..646eace2 100644 --- a/Tests/UnitTestLauncher/UnitTestLauncher.nfproj +++ b/Tests/UnitTestLauncher/UnitTestLauncher.nfproj @@ -27,7 +27,7 @@ - + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 552271dd..8cf81cb4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -60,6 +60,8 @@ jobs: variables: - group: sign-client-credentials + - name: DOTNET_NOLOGO + value: true - name: buildPlatform value: 'Any CPU' - name: buildConfiguration @@ -78,6 +80,7 @@ jobs: runUnitTests: ${{ ne(variables['processPrLabels.SKIP_UNIT_TESTS'], 'true') }} unitTestRunsettings: '$(System.DefaultWorkingDirectory)\.runsettings' skipNuGetCache: true + buildComponentsUsePreview: true # rebuild CoreLibrary project to get the assembly checksum - task: MSBuild@1 diff --git a/nanoFramework.CoreLibrary.nuspec b/nanoFramework.CoreLibrary.nuspec index 63518a26..0dc517b8 100644 --- a/nanoFramework.CoreLibrary.nuspec +++ b/nanoFramework.CoreLibrary.nuspec @@ -18,12 +18,7 @@ nanoFramework C# csharp netmf netnf nanoFramework.CoreLibrary - - - - - - + diff --git a/nanoFramework.CoreLibrary/System/AssemblyInfo.cs b/nanoFramework.CoreLibrary/System/AssemblyInfo.cs index 1d1984ee..8375deaf 100644 --- a/nanoFramework.CoreLibrary/System/AssemblyInfo.cs +++ b/nanoFramework.CoreLibrary/System/AssemblyInfo.cs @@ -10,4 +10,4 @@ [assembly: AssemblyProduct(".NET nanoFramework mscorlib")] [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] -[assembly: AssemblyNativeVersion("100.5.0.24")] +[assembly: AssemblyNativeVersion("100.22.0.0")] diff --git a/nanoFramework.CoreLibrary/System/GC.cs b/nanoFramework.CoreLibrary/System/GC.cs index 13f07e8c..7746f4f3 100644 --- a/nanoFramework.CoreLibrary/System/GC.cs +++ b/nanoFramework.CoreLibrary/System/GC.cs @@ -1,39 +1,79 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; + namespace System { - using Runtime.CompilerServices; - /// /// Controls the system garbage collector, a service that automatically reclaims unused memory. /// public static class GC { +#pragma warning disable S4200 // Native methods should be wrapped + + /// + /// Enables or disables the output of garbage collection messages. + /// + /// to enable the output of GC messages; otherwise, . + /// + /// + /// Enabling GC messages may not always result in output, depending on the target build options. + /// For example, RTM builds, which remove all non-essential features, may not output these messages. + /// + /// + /// This method is specific of .NET nanoFramework implementation. There is no equivalent in full .NET API. + /// + /// [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool AnyPendingFinalizers(); + public static extern void EnableGCMessages(bool enable); /// - /// Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue. + /// Retrieves the heap size excluding fragmentation. For example if the total GC heap size is 1MB and fragmentation, ie, space taken up by free objects, takes up 400kB, this API would report 600kB. A parameter indicates whether this method can wait a short interval before returning, to allow the system to collect garbage and finalize objects. /// - public static void WaitForPendingFinalizers() - { - while (AnyPendingFinalizers()) Threading.Thread.Sleep(10); - } + /// to indicate that this method can wait for garbage collection and heap compaction to occur before returning; otherwise, . + /// The heap size, in bytes, excluding fragmentation. + public static long GetTotalMemory(bool forceFullCollection) => Run(forceFullCollection); /// - /// Requests that the system not call the finalizer for the specified object. + /// Requests that the system call the finalizer for the specified object for which has previously been called. /// - /// The object that a finalizer must not be called for. + /// The object that a finalizer must be called for. + /// is . [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void SuppressFinalize(Object obj); + public static extern void ReRegisterForFinalize(object obj); + + /// + /// Forces an immediate garbage collection of all generations. + /// + /// + /// Use this method to try to reclaim all memory that is inaccessible. It performs a blocking garbage collection of all generations. + /// All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to try to reclaim the maximum amount of available memory. + /// + public static void Collect() => Run(true); /// - /// Requests that the system call the finalizer for the specified object for which SuppressFinalize has previously been called. + /// Requests that the common language runtime not call the finalizer for the specified object. /// - /// The object that a finalizer must be called for. + /// The object whose finalizer must not be executed. + /// is . [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void ReRegisterForFinalize(Object obj); + public static extern void SuppressFinalize(object obj); + /// + /// Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue. + /// + public static void WaitForPendingFinalizers() + { + while (AnyPendingFinalizers()) Threading.Thread.Sleep(10); + } + +#pragma warning restore S4200 // Native methods should be wrapped + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool AnyPendingFinalizers(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint Run(bool compactHeap); } } diff --git a/nanoFramework.CoreLibrary/System/Guid.cs b/nanoFramework.CoreLibrary/System/Guid.cs index e4e2b4f1..7536fd71 100644 --- a/nanoFramework.CoreLibrary/System/Guid.cs +++ b/nanoFramework.CoreLibrary/System/Guid.cs @@ -141,7 +141,7 @@ public Guid(string g) ArgumentNullException.ThrowIfNull(g); #pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - if (!TryParseGuidWithDashes( + if (!TryParse( g, out this)) { @@ -322,8 +322,7 @@ private static char HexToChar(int a) /// /// The .NET nanoFramework implementation of this method only supports 'D' and 'N' format specifiers. /// - [Obsolete("This will be renamed to TryParse in a future version.")] - public static bool TryParseGuidWithDashes( + public static bool TryParse( string input, out Guid result) { diff --git a/nanoFramework.TestFramework b/nanoFramework.TestFramework index 3b48df79..18e4b3d4 160000 --- a/nanoFramework.TestFramework +++ b/nanoFramework.TestFramework @@ -1 +1 @@ -Subproject commit 3b48df792339bc0bcd0a4d6572380821781b65f0 +Subproject commit 18e4b3d4b0c6b647bc3c2a8a0d11a53c059145a7 diff --git a/version.json b/version.json index 7cad52c4..3269dd37 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.17", + "version": "2.0.0-preview.{height}", "assemblyVersion": { "precision": "build" }, @@ -11,6 +11,7 @@ "publicReleaseRefSpec": [ "^refs/heads/develop$", "^refs/heads/main$", + "^refs/heads/generics$", "^refs/heads/v\\d+(?:\\.\\d+)?$" ], "cloudBuild": { From 2bb1d2dcdc5ccee8bc59b28bd17a2e29407bccf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 21 Apr 2025 16:07:29 +0100 Subject: [PATCH 02/17] Work CI-CD - Publish to nuget now happens on build from develop branch too. --- azure-pipelines.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8cf81cb4..e40cd67f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -125,15 +125,17 @@ jobs: - template: azure-pipelines-templates/class-lib-publish.yml@templates - # create or update GitHub release ON tags from release + # create or update GitHub release ON tags - task: GithubRelease@1 condition: >- and( succeeded(), eq(variables['System.PullRequest.PullRequestId'], ''), - startsWith(variables['Build.SourceBranch'], 'refs/heads/main'), - not(contains(variables['Build.SourceBranch'], 'preview')), - eq(variables['StartReleaseCandidate'], false) + eq(variables['StartReleaseCandidate'], false), + or( + eq(variables['Build.SourceBranchName'], 'main'), + eq(variables['Build.SourceBranchName'], 'develop') + ) ) displayName: Create/Update GitHub stable release inputs: From f9a91581913e80320bd7ef00be785fa2b5071072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 21 Apr 2025 17:00:28 +0100 Subject: [PATCH 03/17] Work CI-CD - Temporary disable trigger for tags. ***NO_CI*** --- azure-pipelines.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e40cd67f..676d66d6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,10 +20,6 @@ trigger: - assets/* - nanoFramework.TestFramework/* - tags: - include: - - v* - # PR always trigger build pr: autoCancel: true From e37e4b884817ef802dda6df9ff3df7ac21890a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 21 Apr 2025 17:02:20 +0100 Subject: [PATCH 04/17] Work CI-CD - Fix parameter name. - Add branch to publish nugets from. ***NO_CI*** --- azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 676d66d6..fbfe4dfd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -76,7 +76,7 @@ jobs: runUnitTests: ${{ ne(variables['processPrLabels.SKIP_UNIT_TESTS'], 'true') }} unitTestRunsettings: '$(System.DefaultWorkingDirectory)\.runsettings' skipNuGetCache: true - buildComponentsUsePreview: true + usePreviewBuild: true # rebuild CoreLibrary project to get the assembly checksum - task: MSBuild@1 @@ -119,7 +119,9 @@ jobs: parameters: nugetPackageName: 'nanoFramework.CoreLibrary.NoReflection' - - template: azure-pipelines-templates/class-lib-publish.yml@templates + - template: azure-pipelines-templates/class-lib-publish.yml@templates + parameters: + baseBranchName: 'develop' # create or update GitHub release ON tags - task: GithubRelease@1 From 6303710e27eb44bfe94e91bcfb1d89d73315c992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 30 Apr 2025 16:33:47 +0100 Subject: [PATCH 05/17] Work CI-CD - Add target for .NET Framework v4.7.2 --- nanoFramework.CoreLibrary.nuspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nanoFramework.CoreLibrary.nuspec b/nanoFramework.CoreLibrary.nuspec index 0dc517b8..6b612a06 100644 --- a/nanoFramework.CoreLibrary.nuspec +++ b/nanoFramework.CoreLibrary.nuspec @@ -13,12 +13,14 @@ https://github.com/nanoframework/CoreLibrary images\nf-logo.png - Copyright (c) .NET Foundation and Contributors + Copyright (c) .NET Foundation and Contributors This package includes the CoreLibrary assembly for .NET nanoFramework C# projects. This package requires a target with mscorlib v$nativeVersion$ (checksum $checksum$). In case you don't need the System.Reflection API there is another NuGet package without this API. nanoFramework C# csharp netmf netnf nanoFramework.CoreLibrary + + From 7005c5201e1c76f3b4a39a503fe2d297223c5e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 4 Sep 2025 09:27:44 +0100 Subject: [PATCH 06/17] Fix and improvements in `String` class (#248) ***NO_CI*** --- nanoFramework.CoreLibrary/System/String.cs | 467 ++++++++++++++------- 1 file changed, 310 insertions(+), 157 deletions(-) diff --git a/nanoFramework.CoreLibrary/System/String.cs b/nanoFramework.CoreLibrary/System/String.cs index fdcf2909..c9372ef0 100644 --- a/nanoFramework.CoreLibrary/System/String.cs +++ b/nanoFramework.CoreLibrary/System/String.cs @@ -1,23 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System { - - /* Unmerged change from project 'CoreLibrary' - Before: - using Runtime.CompilerServices; - using System.Collections; - /// - After: - using System.Collections; - using Runtime.CompilerServices; - /// - */ - using System.Collections; - using Runtime.CompilerServices; /// /// Represents text as a sequence of UTF-16 code units. /// @@ -41,7 +30,7 @@ public sealed class String : IComparable, IEnumerable public IEnumerator GetEnumerator() { // Not implemented because of assembly size constraint - // Throw a NotSupportedException in compliance of .net practices + // Throw a NotSupportedException in compliance with .NET practices // (no message to preserve assembly size/memory consumption) // See https://docs.microsoft.com/en-us/dotnet/api/system.notsupportedexception throw new NotSupportedException(); @@ -50,7 +39,7 @@ public IEnumerator GetEnumerator() /// /// Represents the empty string. This field is read-only. /// - public static readonly String Empty = ""; + public static readonly string Empty = ""; /// /// Determines whether this instance and a specified object, which must also be a String object, have the same value. @@ -59,7 +48,7 @@ public IEnumerator GetEnumerator() /// true if obj is a String and its value is the same as this instance; otherwise, false. If obj is null, the method returns false. public override bool Equals(object obj) { - var s = obj as String; + var s = obj as string; return s != null && Equals(this, s); } @@ -68,30 +57,57 @@ public override bool Equals(object obj) /// /// The first string to compare, or null. /// The second string to compare, or null. - /// true if the value of a is the same as the value of b; otherwise, false. If both a and b are null, the method returns true. + /// if the value of is the same as the value of ; otherwise, false. If both and are , the method returns . +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public static extern bool Equals(String a, String b); + public static extern bool Equals(string a, string b); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool Equals(string a, string b); +#endif /// /// Determines whether two specified strings have the same value. /// /// The first string to compare, or null. /// The second string to compare, or null. - /// true if the value of a is the same as the value of b; otherwise, false. + /// if the value of is the same as the value of ; otherwise, . +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public static extern bool operator ==(String a, String b); + public static extern bool operator ==(string? a, string? b); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool operator ==(string a, string b); +#endif /// /// Determines whether two specified strings have different values. /// /// The first string to compare, or null. /// The second string to compare, or null. - /// true if the value of a is different from the value of b; otherwise, false. + /// if the value of is different from the value of ; otherwise, . +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool operator !=(string? a, string? b); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern bool operator !=(String a, String b); + public static extern bool operator !=(string a, string b); +#endif /// - /// Gets the Char object at a specified position in the current String object. + /// Gets the object at a specified position in the current object. /// /// The object at position index. /// A position in the current string. @@ -119,7 +135,7 @@ internal char GetCharByIndex(int index) /// /// The starting position of a substring in this instance. /// The length of the substring in this instance. - /// A Unicode character array whose elements are the length number of characters in this instance starting from character position startIndex. + /// A Unicode character array whose elements are the length number of characters in this instance starting from character position . [MethodImpl(MethodImplOptions.InternalCall)] public extern char[] ToCharArray(int startIndex, int length); @@ -139,35 +155,106 @@ public extern int Length /// Splits a string into substrings that are based on the characters in an array. /// /// A character array that delimits the substrings in this string, an empty array that contains no delimiters, or null. - /// An array whose elements contain the substrings from this instance that are delimited by one or more characters in separator. For more information, see the Remarks section. + /// An array whose elements contain the substrings from this instance that are delimited by one or more characters in . +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public extern String[] Split(params char[] separator); + public extern string[] Split(params char[]? separator); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string[] Split(params char[] separator); +#endif /// /// Splits a string into a maximum number of substrings based on the characters in an array. You also specify the maximum number of substrings to return. /// /// A character array that delimits the substrings in this string, an empty array that contains no delimiters, or null. /// The maximum number of substrings to return. - /// An array whose elements contain the substrings in this instance that are delimited by one or more characters in separator. For more information, see the Remarks section. + /// An array whose elements contain the substrings in this instance that are delimited by one or more characters in +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string[] Split(char[]? separator, int count); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public extern String[] Split(char[] separator, int count); + public extern string[] Split(char[] separator, int count); +#endif /// /// Retrieves a substring from this instance. The substring starts at a specified character position and continues to the end of the string. /// /// The zero-based starting character position of a substring in this instance. - /// A string that is equivalent to the substring that begins at startIndex in this instance, or Empty if startIndex is equal to the length of this instance. + /// A string that is equivalent to the substring that begins at in this instance, or if is equal to the length of this instance. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String Substring(int startIndex); + public extern string Substring(int startIndex); /// /// Retrieves a substring from this instance. The substring starts at a specified character position and has a specified length. /// /// The zero-based starting character position of a substring in this instance. /// The number of characters in the substring. - /// A string that is equivalent to the substring of length length that begins at startIndex in this instance, or Empty if startIndex is equal to the length of this instance and length is zero. + /// A string that is equivalent to the substring of length that begins at in this instance, or if is equal to the length of this instance and length is zero. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string Substring(int startIndex, int length); + + /// + /// Removes all leading and trailing white-space characters from the current string. + /// + /// The string that remains after all white-space characters are removed from the start and end of the current string. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string Trim(); + +#if NANOCLR_REFLECTION + +#nullable enable + + /// + /// Removes all the leading occurrences of a set of characters specified in an array from the current string. + /// + /// An array of Unicode characters to remove, or . + /// The string that remains after all occurrences of characters in the parameter are removed from the start of the current string. If is or an empty array, white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String Substring(int startIndex, int length); + public extern string Trim(params char[]? trimChars); + + /// + /// Removes all the leading occurrences of a set of characters specified in an array from the current string. + /// + /// The string that remains after all occurrences of characters in the trimChars parameter are removed from the start of the current string. If trimChars is null or an empty array, white-space characters are removed instead. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string TrimStart(); + + /// + /// Removes all the leading occurrences of a set of characters specified in an array from the current string. + /// + /// An array of Unicode characters to remove, or . + /// The string that remains after all occurrences of characters in the parameter are removed from the start of the current string. If is or an empty array, white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string TrimStart(params char[]? trimChars); + + /// + /// Removes all the trailing occurrences of a set of characters specified in an array from the current string. + /// + /// The string that remains after all white-space characters are removed from the end of the current string. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string TrimEnd(); + + /// + /// Removes all the trailing occurrences of a set of characters specified in an array from the current string. + /// + /// An array of Unicode characters to remove, or . + /// The string that remains after all occurrences of the characters in the parameter are removed from the end of the current string. If is or an empty array, Unicode white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern string TrimEnd(params char[]? trimChars); + +#nullable restore + +#else /// /// Removes all leading and trailing occurrences of a set of characters specified in an array from the current String object. @@ -177,23 +264,25 @@ public extern int Length /// If trimChars is null or an empty array, white-space characters are removed instead. If no characters can be trimmed from the current instance, /// the method returns the current instance unchanged. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String Trim(params char[] trimChars); + public extern string Trim(params char[] trimChars); /// - /// Removes all leading occurrences of a set of characters specified in an array from the current String object. + /// Removes all the leading occurrences of a set of characters specified in an array from the current string. /// - /// An array of Unicode characters to remove, or null. - /// The string that remains after all occurrences of characters in the trimChars parameter are removed from the start of the current string. If trimChars is null or an empty array, white-space characters are removed instead. + /// An array of Unicode characters to remove, or . + /// The string that remains after all occurrences of characters in the parameter are removed from the start of the current string. If is or an empty array, white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String TrimStart(params char[] trimChars); + public extern string TrimStart(params char[] trimChars); /// - /// Removes all trailing occurrences of a set of characters specified in an array from the current String object. + /// Removes all the trailing occurrences of a set of characters specified in an array from the current string. /// - /// An array of Unicode characters to remove, or null. - /// The string that remains after all occurrences of the characters in the trimChars parameter are removed from the end of the current string. If trimChars is null or an empty array, Unicode white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. + /// An array of Unicode characters to remove, or . + /// The string that remains after all occurrences of the characters in the parameter are removed from the end of the current string. If is or an empty array, Unicode white-space characters are removed instead. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String TrimEnd(params char[] trimChars); + public extern string TrimEnd(params char[] trimChars); + +#endif /// /// Initializes a new instance of the String class to the value indicated by an array of Unicode characters, a starting character position within that array, and a length. @@ -224,31 +313,63 @@ public extern int Length /// /// The first string to compare. /// The second string to compare. - /// A 32-bit signed integer that indicates the lexical relationship between the two comparands. + /// + /// A 32-bit signed integer that indicates the lexical relationship between the two comparands. + /// Less than zero : precedes in the sort order. + /// Zero : occurs in the same position as in the sort order. + /// Greater than zero : follows in the sort order. + /// +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public static extern int Compare(String strA, String strB); + public static extern int Compare(string? strA, string? strB); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern int Compare(string strA, string strB); +#endif /// - /// Compares this instance with a specified Object and indicates whether this instance precedes, follows, or appears in the same position in the sort order as the specified Object. + /// Compares this instance with a specified and indicates whether this instance precedes, follows, or appears in the same position in the sort order as the specified . /// - /// An object that evaluates to a String. - /// A 32-bit signed integer that indicates whether this instance precedes, follows, or appears in the same position in the sort order as the value parameter. + /// An object that evaluates to a . + /// A 32-bit signed integer that indicates whether this instance precedes, follows, or appears in the same position in the sort order as the parameter. +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public extern int CompareTo(Object value); + public extern int CompareTo(object? value); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public extern int CompareTo(object value); +#endif /// - /// Compares this instance with a specified String object and indicates whether this instance precedes, follows, or appears in the same position in the sort order as the specified string. + /// Compares this instance with a specified object and indicates whether this instance precedes, follows, or appears in the same position in the sort order as the specified string. /// /// The string to compare with this instance. - /// A 32-bit signed integer that indicates whether this instance precedes, follows, or appears in the same position in the sort order as the strB parameter. + /// A 32-bit signed integer that indicates whether this instance precedes, follows, or appears in the same position in the sort order as the parameter. +#if NANOCLR_REFLECTION + +#nullable enable [MethodImpl(MethodImplOptions.InternalCall)] - public extern int CompareTo(String strB); + public extern int CompareTo(string? strB); +#nullable restore + +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public extern int CompareTo(string strB); +#endif /// /// Reports the zero-based index of the first occurrence of the specified Unicode character in this string. /// /// A Unicode character to seek. - /// The zero-based index position of value if that character is found, or -1 if it is not. + /// The zero-based index position of if that character is found, or -1 if it is not. [MethodImpl(MethodImplOptions.InternalCall)] public extern int IndexOf(char value); @@ -257,7 +378,7 @@ public extern int Length /// /// A Unicode character to seek. /// The search starting position. - /// The zero-based index position of value from the start of the string if that character is found, or -1 if it is not. + /// The zero-based index position of from the start of the string if that character is found, or -1 if it is not. [MethodImpl(MethodImplOptions.InternalCall)] public extern int IndexOf(char value, int startIndex); @@ -267,69 +388,69 @@ public extern int Length /// A Unicode character to seek. /// The search starting position. /// The number of character positions to examine. - /// The zero-based index position of value if that character is found, or -1 if it is not. + /// The zero-based index position of if that character is found, or -1 if it is not. [MethodImpl(MethodImplOptions.InternalCall)] public extern int IndexOf(char value, int startIndex, int count); /// - /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. + /// Reports the zero-based index of the first occurrence of the specified string in this instance. /// - /// A Unicode character array containing one or more characters to seek. - /// The zero-based index position of the first occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found. + /// The string to seek. + /// The zero-based index position of if that string is found, or -1 if it is not. If is , the return value is 0. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOfAny(char[] anyOf); + public extern int IndexOf(string value); /// - /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position. + /// Reports the zero-based index of the first occurrence of the specified string in this instance. The search starts at a specified character position. /// - /// A Unicode character array containing one or more characters to seek. + /// The string to seek. /// The search starting position. - /// The zero-based index position of the first occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found. + /// The zero-based index position of from the start of the current instance if that string is found, or -1 if it is not. If is , the return value is . [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOfAny(char[] anyOf, int startIndex); + public extern int IndexOf(string value, int startIndex); /// - /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position and examines a specified number of character positions. + /// Reports the zero-based index of the first occurrence of the specified string in this instance. The search starts at a specified character position and examines a specified number of character positions. /// - /// A Unicode character array containing one or more characters to seek. + /// The string to seek. /// The search starting position. /// The number of character positions to examine. - /// The zero-based index position of the first occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found. + /// The zero-based index position of value from the start of the current instance if that string is found, or -1 if it is not. If value is String.Empty, the return value is startIndex. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOfAny(char[] anyOf, int startIndex, int count); + public extern int IndexOf(string value, int startIndex, int count); /// - /// Reports the zero-based index of the first occurrence of the specified string in this instance. + /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. /// - /// The string to seek. - /// The zero-based index position of value if that string is found, or -1 if it is not. If value is String.Empty, the return value is 0. + /// A Unicode character array containing one or more characters to seek. + /// The zero-based index position of the first occurrence in this instance where any character in was found; -1 if no character in was found. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOf(String value); + public extern int IndexOfAny(char[] anyOf); /// - /// Reports the zero-based index of the first occurrence of the specified string in this instance. The search starts at a specified character position. + /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position. /// - /// The string to seek. + /// A Unicode character array containing one or more characters to seek. /// The search starting position. - /// The zero-based index position of value from the start of the current instance if that string is found, or -1 if it is not. If value is String.Empty, the return value is startIndex. + /// The zero-based index position of the first occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOf(String value, int startIndex); + public extern int IndexOfAny(char[] anyOf, int startIndex); /// - /// Reports the zero-based index of the first occurrence of the specified string in this instance. The search starts at a specified character position and examines a specified number of character positions. + /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array of Unicode characters. The search starts at a specified character position and examines a specified number of character positions. /// - /// The string to seek. + /// A Unicode character array containing one or more characters to seek. /// The search starting position. /// The number of character positions to examine. - /// The zero-based index position of value from the start of the current instance if that string is found, or -1 if it is not. If value is String.Empty, the return value is startIndex. + /// The zero-based index position of the first occurrence in this instance where any character in was found; -1 if no character in was found. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int IndexOf(String value, int startIndex, int count); + public extern int IndexOfAny(char[] anyOf, int startIndex, int count); /// /// Reports the zero-based index position of the last occurrence of a specified Unicode character within this instance. /// /// The Unicode character to seek. - /// The zero-based index position of value if that character is found, or -1 if it is not. + /// The zero-based index position of if that character is found, or -1 if it is not. [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOf(char value); @@ -337,8 +458,8 @@ public extern int Length /// Reports the zero-based index position of the last occurrence of a specified Unicode character within this instance. The search starts at a specified character position and proceeds backward toward the beginning of the string. /// /// The Unicode character to seek. - /// The starting position of the search. The search proceeds from startIndex toward the beginning of this instance. - /// The zero-based index position of value if that character is found, or -1 if it is not found or if the current instance equals String.Empty. + /// The starting position of the search. The search proceeds from toward the beginning of this instance. + /// The zero-based index position of if that character is found, or -1 if it is not found or if the current instance equals . [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOf(char value, int startIndex); @@ -346,9 +467,9 @@ public extern int Length /// Reports the zero-based index position of the last occurrence of the specified Unicode character in a substring within this instance. The search starts at a specified character position and proceeds backward toward the beginning of the string for a specified number of character positions. /// /// The Unicode character to seek. - /// The starting position of the search. The search proceeds from startIndex toward the beginning of this instance. + /// The starting position of the search. The search proceeds from toward the beginning of this instance. /// The number of character positions to examine. - /// The zero-based index position of value if that character is found, or -1 if it is not found or if the current instance equals String.Empty. + /// The zero-based index position of if that character is found, or -1 if it is not found or if the current instance equals . [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOf(char value, int startIndex, int count); @@ -356,7 +477,7 @@ public extern int Length /// Reports the zero-based index position of the last occurrence in this instance of one or more characters specified in a Unicode array. /// /// A Unicode character array containing one or more characters to seek. - /// The index position of the last occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found. + /// The index position of the last occurrence in this instance where any character in was found; -1 if no character in was found. [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOfAny(char[] anyOf); @@ -365,7 +486,7 @@ public extern int Length /// /// A Unicode character array containing one or more characters to seek. /// The search starting position. The search proceeds from startIndex toward the beginning of this instance. - /// The index position of the last occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found or if the current instance equals String.Empty. + /// The index position of the last occurrence in this instance where any character in was found; -1 if no character in was found or if the current instance equals . [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOfAny(char[] anyOf, int startIndex); @@ -375,7 +496,7 @@ public extern int Length /// A Unicode character array containing one or more characters to seek. /// The search starting position. The search proceeds from startIndex toward the beginning of this instance. /// The number of character positions to examine. - /// The index position of the last occurrence in this instance where any character in anyOf was found; -1 if no character in anyOf was found or if the current instance equals String.Empty. + /// The index position of the last occurrence in this instance where any character in was found; -1 if no character in was found or if the current instance equals . [MethodImpl(MethodImplOptions.InternalCall)] public extern int LastIndexOfAny(char[] anyOf, int startIndex, int count); @@ -383,18 +504,18 @@ public extern int Length /// Reports the zero-based index position of the last occurrence of a specified string within this instance. /// /// The string to seek. - /// The zero-based starting index position of value if that string is found, or -1 if it is not. If value is String.Empty, the return value is the last index position in this instance. + /// The zero-based starting index position of if that string is found, or -1 if it is not. If is , the return value is the last index position in this instance. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int LastIndexOf(String value); + public extern int LastIndexOf(string value); /// /// Reports the zero-based index position of the last occurrence of a specified string within this instance. The search starts at a specified character position and proceeds backward toward the beginning of the string. /// /// The string to seek. /// The search starting position. The search proceeds from startIndex toward the beginning of this instance. - /// The zero-based starting index position of value if that string is found, or -1 if it is not found or if the current instance equals String.Empty. If value is String.Empty, the return value is the smaller of startIndex and the last index position in this instance. + /// The zero-based starting index position of if that string is found, or -1 if it is not found or if the current instance equals . If is , the return value is the smaller of and the last index position in this instance. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int LastIndexOf(String value, int startIndex); + public extern int LastIndexOf(string value, int startIndex); /// /// Reports the zero-based index position of the last occurrence of a specified string within this instance. The search starts at a specified character position and proceeds backward toward the beginning of the string for a specified number of character positions. @@ -402,45 +523,39 @@ public extern int Length /// The string to seek. /// The search starting position. The search proceeds from startIndex toward the beginning of this instance. /// The number of character positions to examine. - /// The zero-based starting index position of value if that string is found, or -1 if it is not found or if the current instance equals String.Empty. If value is Empty, the return value is the smaller of startIndex and the last index position in this instance. + /// The zero-based starting index position of if that string is found, or -1 if it is not found or if the current instance equals . If is , the return value is the smaller of and the last index position in this instance. [MethodImpl(MethodImplOptions.InternalCall)] - public extern int LastIndexOf(String value, int startIndex, int count); + public extern int LastIndexOf(string value, int startIndex, int count); /// /// Returns a copy of this string converted to lowercase. /// /// A string in lowercase. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String ToLower(); + public extern string ToLower(); /// /// Returns a copy of this string converted to uppercase. /// /// The uppercase equivalent of the current string. [MethodImpl(MethodImplOptions.InternalCall)] - public extern String ToUpper(); + public extern string ToUpper(); /// /// Returns this instance of String; no actual conversion is performed. /// /// The current string. - public override String ToString() + public override string ToString() { return this; } - /// - /// Removes all leading and trailing white-space characters from the current String object. - /// - /// The string that remains after all white-space characters are removed from the start and end of the current string. If no characters can be trimmed from the current instance, the method returns the current instance unchanged. - [MethodImpl(MethodImplOptions.InternalCall)] - public extern String Trim(); /// /// Creates the string representation of a specified object. /// /// The object to represent, or null. /// The string representation of the value of arg0, or String.Empty if arg0 is null. - public static String Concat(Object arg0) + public static string Concat(object arg0) { return arg0 == null ? Empty : arg0.ToString(); } @@ -451,17 +566,11 @@ public static String Concat(Object arg0) /// The first object to concatenate. /// The second object to concatenate. /// The concatenated string representations of the values of arg0 and arg1. - public static String Concat(Object arg0, Object arg1) + public static string Concat(object arg0, object arg1) { - if (arg0 == null) - { - arg0 = Empty; - } + arg0 ??= Empty; - if (arg1 == null) - { - arg1 = Empty; - } + arg1 ??= Empty; return Concat(arg0.ToString(), arg1.ToString()); } @@ -473,22 +582,11 @@ public static String Concat(Object arg0, Object arg1) /// The second object to concatenate. /// The third object to concatenate. /// The concatenated string representations of the values of arg0, arg1 and arg2. - public static String Concat(Object arg0, Object arg1, Object arg2) + public static string Concat(object arg0, object arg1, object arg2) { - if (arg0 == null) - { - arg0 = Empty; - } - - if (arg1 == null) - { - arg1 = Empty; - } - - if (arg2 == null) - { - arg2 = Empty; - } + arg0 ??= Empty; + arg1 ??= Empty; + arg2 ??= Empty; return Concat(arg0.ToString(), arg1.ToString(), arg2.ToString()); } @@ -499,14 +597,17 @@ public static String Concat(Object arg0, Object arg1, Object arg2) /// An object array that contains the elements to concatenate. /// The concatenated string representations of the values of the elements in args. /// - public static String Concat(params Object[] args) + public static string Concat(params object[] args) { - if (args == null) throw new ArgumentNullException("args"); + if (args == null) + { + throw new ArgumentNullException("args"); + } - var length = args.Length; - var sArgs = new String[length]; + int length = args.Length; + string[] sArgs = new string[length]; - for (var i = 0; i < length; i++) + for (int i = 0; i < length; i++) { sArgs[i] = args[i] == null ? Empty : args[i].ToString(); } @@ -515,59 +616,95 @@ public static String Concat(params Object[] args) } /// - /// Concatenates two specified instances of String. + /// Concatenates two specified instances of . /// /// The first string to concatenate. /// The second string to concatenate. - /// The concatenation of str0 and str1. + /// The concatenation of and . +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Concat(string? str0, string? str1); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern String Concat(String str0, String str1); + public static extern string Concat(string str0, string str1); +#endif /// - /// Concatenates three specified instances of String. + /// Concatenates three specified instances of . /// /// The first string to concatenate. /// The second string to concatenate. /// The third string to concatenate. - /// The concatenation of str0, str1 and str2. + /// The concatenation of , and . +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Concat(string? str0, string? str1, string? str2); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern String Concat(String str0, String str1, String str2); + public static extern string Concat(string str0, string str1, string str2); +#endif /// - /// Concatenates four specified instances of String. + /// Concatenates four specified instances of . /// /// The first string to concatenate. /// The second string to concatenate. /// The third string to concatenate. /// The fourth string to concatenate. /// The concatenation of str0, str1, str2 and str3. +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Concat(string? str0, string? str1, string? str2, string? str3); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern String Concat(String str0, String str1, String str2, String str3); + public static extern string Concat(string str0, string str1, string str2, string str3); +#endif /// - /// Concatenates the elements of a specified String array. + /// Concatenates the elements of a specified array. /// /// An array of string instances. - /// The concatenated elements of values. + /// The concatenated elements of . +#if NANOCLR_REFLECTION + +#nullable enable + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string Concat(params string?[] values); +#nullable restore + +#else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern String Concat(params String[] values); + public static extern string Concat(params string[] values); +#endif /// - /// Retrieves the system's reference to the specified String. + /// Retrieves the system's reference to the specified . /// /// A string to search for in the intern pool. - /// The system's reference to str, if it is interned; otherwise, a new reference to a string with the value of str. - public static String Intern(String str) + /// The system's reference to , if it is interned; otherwise, a new reference to a string with the value of . + public static string Intern(string str) { return str; } /// - /// Retrieves a reference to a specified String. + /// Retrieves a reference to a specified . /// /// The string to search for in the intern pool. - /// A reference to str if it is in the common language runtime intern pool; otherwise, null. - public static String IsInterned(String str) + /// A reference to if it is in the common language runtime intern pool; otherwise, . + public static string IsInterned(string str) { return str; } @@ -577,8 +714,16 @@ public static String IsInterned(String str) /// /// A composite format string /// An object array that contains zero or more objects to format. - /// A copy of format in which the format items have been replaced by the string representation of the corresponding objects in args. + /// A copy of in which the format items have been replaced by the string representation of the corresponding objects in . +#if NANOCLR_REFLECTION + +#nullable enable + public static string Format(string format, params object?[] args) +#nullable restore + +#else public static string Format(string format, params object[] args) +#endif { var index = 0; var alignment = 0; @@ -785,13 +930,13 @@ public static string Format(string format, params object[] args) /// /// The number of characters in the resulting string, equal to the number of original characters plus any additional padding characters. /// A Unicode padding character. - /// - public String PadLeft(int totalWidth, char paddingChar = ' ') + /// A new string that is equivalent to this instance, but right-aligned and padded on the left with as many characters as needed to create a length of . However, if is less than the length of this instance, the method returns a reference to the existing instance. If is equal to the length of this instance, the method returns a new string that is identical to this instance. + public string PadLeft(int totalWidth, char paddingChar = ' ') { if (totalWidth < 0) { #pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentOutOfRangeException("totalWidth can't be less than 0"); + throw new ArgumentOutOfRangeException(); #pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one } @@ -801,7 +946,7 @@ public String PadLeft(int totalWidth, char paddingChar = ' ') } else { - return new String(paddingChar, totalWidth - Length) + this; + return new string(paddingChar, totalWidth - Length) + this; } } @@ -810,13 +955,13 @@ public String PadLeft(int totalWidth, char paddingChar = ' ') /// /// The number of characters in the resulting string, equal to the number of original characters plus any additional padding characters. /// A Unicode padding character. - /// - public String PadRight(int totalWidth, char paddingChar = ' ') + /// A new string that is equivalent to this instance, but left-aligned and padded on the right with as many characters as needed to create a length of . However, if is less than the length of this instance, the method returns a reference to the existing instance. If is equal to the length of this instance, the method returns a new string that is identical to this instance. + public string PadRight(int totalWidth, char paddingChar = ' ') { if (totalWidth < 0) { #pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one - throw new ArgumentOutOfRangeException("totalWidth can't be less than 0"); + throw new ArgumentOutOfRangeException(); #pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one } @@ -826,7 +971,7 @@ public String PadRight(int totalWidth, char paddingChar = ' ') } else { - return this + new String(paddingChar, totalWidth - Length); + return this + new string(paddingChar, totalWidth - Length); } } @@ -835,7 +980,15 @@ public String PadRight(int totalWidth, char paddingChar = ' ') /// /// The string to test. /// if the value parameter is or an empty string (""); otherwise, . - public static bool IsNullOrEmpty([NotNullWhen(false)] string value) +#if NANOCLR_REFLECTION + +#nullable enable + public static bool IsNullOrEmpty([NotNullWhen(false)] string? value) +#nullable restore + +#else + public static bool IsNullOrEmpty(string value) +#endif { return value == null || value.Length == 0; } From ed90c8ee69bb2c65d0b71af1ec4c5342fa2cd216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 11 Sep 2025 13:01:31 +0100 Subject: [PATCH 07/17] Add `Span` and `ReadOnlySpan` (#249) --- .../NFUnitTestSystemLib.nfproj | 1 + Tests/NFUnitTestSystemLib/UnitTestNullable.cs | 266 +++++++++++++++ .../UnitTestReflectionTypeTest.cs | 2 + Tests/NFUnitTestTypes/UnitTestsSpanByte.cs | 124 ++++--- nanoFramework.CoreLibrary/CoreLibrary.nfproj | 13 +- nanoFramework.CoreLibrary/System/Array.cs | 21 +- .../System/ArrayTypeMismatchException.cs | 38 +++ .../System/Collections/Generic/Comparer.cs | 129 +++++++ .../Collections/Generic/ComparerHelpers.cs | 93 +++++ .../System/Collections/Generic/IComparer.cs | 22 ++ .../Diagnostics/StackTraceHiddenAttribute.cs | 18 + nanoFramework.CoreLibrary/System/Nullable.cs | 138 ++++++++ .../System/ReadOnlySpan.cs | 193 +++++++++++ .../Runtime/InteropServices/InAttribute .cs | 19 ++ nanoFramework.CoreLibrary/System/Span.cs | 320 ++++++++++++++++++ .../System/SpanDebugView.cs | 27 ++ nanoFramework.CoreLibrary/System/Type.cs | 38 +++ 17 files changed, 1408 insertions(+), 54 deletions(-) create mode 100644 Tests/NFUnitTestSystemLib/UnitTestNullable.cs create mode 100644 nanoFramework.CoreLibrary/System/ArrayTypeMismatchException.cs create mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs create mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs create mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs create mode 100644 nanoFramework.CoreLibrary/System/Diagnostics/StackTraceHiddenAttribute.cs create mode 100644 nanoFramework.CoreLibrary/System/Nullable.cs create mode 100644 nanoFramework.CoreLibrary/System/ReadOnlySpan.cs create mode 100644 nanoFramework.CoreLibrary/System/Runtime/InteropServices/InAttribute .cs create mode 100644 nanoFramework.CoreLibrary/System/Span.cs create mode 100644 nanoFramework.CoreLibrary/System/SpanDebugView.cs diff --git a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj index 593d741a..d3cc5036 100644 --- a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj +++ b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj @@ -24,6 +24,7 @@ + diff --git a/Tests/NFUnitTestSystemLib/UnitTestNullable.cs b/Tests/NFUnitTestSystemLib/UnitTestNullable.cs new file mode 100644 index 00000000..74a98741 --- /dev/null +++ b/Tests/NFUnitTestSystemLib/UnitTestNullable.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using nanoFramework.TestFramework; + +namespace NFUnitTestSystemLib +{ + [TestClass] + public class UnitTestNullable + { + [TestMethod] + public void Ctor_Empty() + { + // Nullable and Nullable are mostly verbatim ports so we don't test much here. + + int? n = default(int?); + Assert.IsFalse(n.HasValue); + + // TODO replace with Assert.ThrowsException when available + Assert.ThrowsException( + typeof(InvalidOperationException), + () => _ = n.Value); + Assert.ThrowsException( + typeof(InvalidOperationException), + () => _ = (int)n); + + Assert.IsNull(n); + Assert.AreNotEqual(7, n); + Assert.AreEqual(0, n.GetHashCode()); + Assert.AreEqual("", n.ToString()); + Assert.AreEqual(default(int), n.GetValueOrDefault()); + Assert.AreEqual(999, n.GetValueOrDefault(999)); + + n = new int?(42); + Assert.IsTrue(n.HasValue); + Assert.AreEqual(42, n.Value); + Assert.AreEqual(42, (int)n); + Assert.IsNotNull(n); + Assert.AreNotEqual(7, n); + Assert.AreEqual(42, n); + Assert.AreEqual(42.GetHashCode(), n.GetHashCode()); + Assert.AreEqual(42.ToString(), n.ToString()); + Assert.AreEqual(42, n.GetValueOrDefault()); + Assert.AreEqual(42, n.GetValueOrDefault(999)); + + n = 88; + Assert.IsTrue(n.HasValue); + Assert.AreEqual(88, n.Value); + } + + [TestMethod] + public static void Boxing() + { + int? n = new int?(42); + Unbox(n); + } + + private static void Unbox(object o) + { + Type t = o.GetType(); + + // TOOD: replace with Assert.IsNotType(t); when available + Assert.IsNotInstanceOfType(typeof(int?), t); + + Assert.AreEqual(typeof(int), t); + } + + [TestMethod] + public static void ExplicitCast_T() + { + int? nullable = 5; + int value = (int)nullable; + Assert.AreEqual(5, value); + + nullable = null; + // TODO replace with Assert.Throws(() => (int)nullable); when available + Assert.ThrowsException( + typeof(InvalidOperationException), + () => _ = (int)nullable); + } + + [TestMethod] + public static void GetUnderlyingType() + { + Assert.AreEqual(typeof(int), Nullable.GetUnderlyingType(typeof(int?))); + Assert.AreEqual(null, Nullable.GetUnderlyingType(typeof(int))); + Assert.AreEqual(null, Nullable.GetUnderlyingType(typeof(G))); + } + + [TestMethod] + public static void GetUnderlyingType_NullType_ThrowsArgumentNullException() + { + // TODO replace with Assert.Throws("nullableType", () => Nullable.GetUnderlyingType(null)); when available + Assert.ThrowsException( + typeof(ArgumentNullException), + () => Nullable.GetUnderlyingType(null) + ); + } + + // TODO: Uncomment when available + //[TestMethod] + //public static void GetValueRefOrDefaultRef_WithValue() + //{ + // static void Test(T before, T after) + // where T : struct + // { + // T? nullable = before; + // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable); + + // Assert.AreEqual(before, nullable!.Value); + + // Unsafe.AsRef(in reference) = after; + + // Assert.Equal(after, nullable.Value); + // } + + // Test((byte)0, (byte)42); + // Test(0, 42); + // Test(1.3f, 3.14f); + // Test(0.555, 8.49); + // Test(Guid.NewGuid(), Guid.NewGuid()); + //} + + // TODO: Uncomment when available + //[TestMethod] + //public static void GetValueRefOrDefaultRef_WithDefault() + //{ + // static void Test() + // where T : struct + // { + // T? nullable = null; + // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable); + + // Assert.Equal(nullable!.GetValueOrDefault(), reference); + // } + + // Test(); + // Test(); + // Test(); + // Test(); + // Test(); + //} + + // TODO: Uncomment when available + //[TestMethod] + //public static void GetValueRefOrDefaultRef_UnsafeWriteToNullMaintainsExpectedBehavior() + //{ + // static void Test(T after) + // where T : struct + // { + // T? nullable = null; + // ref readonly T reference = ref Nullable.GetValueRefOrDefaultRef(in nullable); + + // Unsafe.AsRef(in reference) = after; + + // Assert.Equal(after, nullable.GetValueOrDefault()); // GetValueOrDefault() unconditionally returns the field + // Assert.False(nullable.HasValue); + // Assert.Equal(0, nullable.GetHashCode()); // GetHashCode() returns 0 if HasValue is false, without reading the field + // Assert.Throws(() => nullable.Value); // Accessing the value should still throw despite the write + // Assert.Throws(() => (T)nullable); + // } + + // Test((byte)42); + // Test(42); + // Test(3.14f); + // Test(8.49); + // Test(Guid.NewGuid()); + //} + + [TestMethod] + public static void Compare_Equals() + { + // Case 1: (null, null, 0) + int? n1 = null; + int? n2 = null; + int expected = 0; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + + // Case 2: (7, null, 1) + n1 = 7; + n2 = null; + expected = 1; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + + // Case 3: (null, 7, -1) + n1 = null; + n2 = 7; + expected = -1; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + + // Case 4: (7, 7, 0) + n1 = 7; + n2 = 7; + expected = 0; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + + // Case 5: (7, 5, 1) + n1 = 7; + n2 = 5; + expected = 1; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + + // Case 6: (5, 7, -1) + n1 = 5; + n2 = 7; + expected = -1; + Assert.AreEqual(expected == 0, Nullable.Equals(n1, n2)); + Assert.AreEqual(expected == 0, n1.Equals(n2)); + // TODO Assert.AreEqual(expected, Nullable.Compare(n1, n2)); + } + + //[TestMethod] + //public static void MutatingMethods_MutationsAffectOriginal() + //{ + // MutatingStruct? ms = new MutatingStruct() { Value = 1 }; + + // for (int i = 1; i <= 2; i++) + // { + // Assert.AreEqual(i.ToString(), ms.Value.ToString()); + // Assert.AreEqual(i, ms.Value.Value); + + // Assert.AreEqual(i.ToString(), ms.ToString()); + // Assert.AreEqual(i + 1, ms.Value.Value); + // } + + // for (int i = 3; i <= 4; i++) + // { + // Assert.AreEqual(i, ms.Value.GetHashCode()); + // Assert.AreEqual(i, ms.Value.Value); + + // Assert.AreEqual(i, ms.GetHashCode()); + // Assert.AreEqual(i + 1, ms.Value.Value); + // } + + // for (int i = 5; i <= 6; i++) + // { + // ms.Value.Equals(new object()); + // Assert.AreEqual(i, ms.Value.Value); + + // ms.Equals(new object()); + // Assert.AreEqual(i + 1, ms.Value.Value); + // } + //} + + //private struct MutatingStruct + //{ + // public int Value; + // public override string ToString() => Value++.ToString(); + // public override bool Equals(object obj) => Value++.Equals(null); + // public override int GetHashCode() => Value++.GetHashCode(); + //} + + public class G { } + } +} diff --git a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs index 471b394d..805c2b93 100644 --- a/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs +++ b/Tests/NFUnitTestSystemLib/UnitTestReflectionTypeTest.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Reflection; using nanoFramework.TestFramework; +using static NFUnitTestSystemLib.UnitTestNullable; namespace NFUnitTestSystemLib { @@ -163,6 +164,7 @@ public void SystemReflectionType_SystemType_Test2() Assert.IsTrue(typeof(Array).IsInstanceOfType(blah)); Assert.IsTrue(typeof(TestStruct[]).IsArray); + Assert.IsTrue(typeof(G[]).IsArray); Assert.IsFalse(typeof(Array).IsValueType); Assert.IsTrue(typeof(TestStruct).IsValueType); Assert.IsTrue(typeof(Type).IsSubclassOf(typeof(MemberInfo))); diff --git a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs index e4ffde13..9ed5dec8 100644 --- a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs +++ b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs @@ -13,13 +13,15 @@ public class UnitTestsSpanByte public void EmptySpanTests() { // Empty span - SpanByte span = SpanByte.Empty; + Span span = Span.Empty; // Create a destination span larger - SpanByte destination = new byte[1]; + Span destination = new Span(new byte[1]); + span.CopyTo(destination); // Now also empty - destination = SpanByte.Empty; + destination = Span.Empty; + span.CopyTo(destination); } @@ -27,54 +29,56 @@ public void EmptySpanTests() public void RaisingExceptionsOfAllKindsTests() { // Should raise an exception on creation - Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2"); - Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2"); - Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2"); - Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0"); // Exception on index access byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var data = span[span.Length]; + Span span = new Span(array); + _ = span[span.Length]; }); - Assert.ThrowsException(typeof(IndexOutOfRangeException), () => + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var data = span[-1]; + Span span = new Span(array); + _ = span[-1]; }); // Copy to with too small destination Assert.ThrowsException(typeof(ArgumentException), () => { - SpanByte span = new SpanByte(array); - SpanByte destination = new byte[span.Length - 1]; + Span span = new Span(array); + Span destination = new Span(new byte[span.Length - 1]); + span.CopyTo(destination); }); // Slicing arguments Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var sliced = span.Slice(span.Length + 1); + Span span = new Span(array); + _ = span.Slice(span.Length + 1); }); Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var sliced = span.Slice(1, span.Length); + Span span = new Span(array); + _ = span.Slice(1, span.Length); }); Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var sliced = span.Slice(-1, span.Length); + Span span = new Span(array); + _ = span.Slice(-1, span.Length); }); Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { - SpanByte span = new SpanByte(array); - var sliced = span.Slice(1, -1); + Span span = new Span(array); + _ = span.Slice(1, -1); }); } @@ -84,7 +88,7 @@ public void ToArrayTest() { byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; - SpanByte span = new(array); + Span span = new(array); byte[] toArray = span.ToArray(); @@ -95,28 +99,33 @@ public void ToArrayTest() public void ConstructorsOfAllKindsTests() { // Empty span - SpanByte span = new SpanByte(); + Span span = new Span(); + Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0"); Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty"); // Empty span - span = new SpanByte(null, 0, 0); + span = new Span(null, 0, 0); + Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0"); Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty"); // Empty span - span = SpanByte.Empty; + span = Span.Empty; + Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0"); Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty"); - // Span from normal array + // Spanfrom normal array byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; - span = new SpanByte(array); + span = new Span(array); + Assert.AreEqual(span.Length, array.Length, $"SpanByte should have length of the array it takes: {array.Length}"); Assert.IsFalse(span.IsEmpty, "SpanByte should NOT be IsEmpty"); - // Span from normal array with different start and length - span = new SpanByte(array, 2, 8); + // Spanfrom normal array with different start and length + span = new Span(array, 2, 8); + Assert.AreEqual(span.Length, 8, $"SpanByte should have length of 8"); Assert.IsFalse(span.IsEmpty, "SpanByte should NOT be IsEmpty"); } @@ -124,17 +133,20 @@ public void ConstructorsOfAllKindsTests() [TestMethod] public void SliceTests() { - // Span from normal array + // Spanfrom normal array byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; - SpanByte span = new SpanByte(array); + Span span = new Span(array); + // Slice 2 elements and check - var sliced = span.Slice(0, 2); + Span sliced = span.Slice(0, 2); + Assert.AreEqual(sliced.Length, 2, "Sliced span lenght must be 2"); Assert.AreEqual(sliced[0], (byte)0x00, "Sliced first element must be value 0"); Assert.AreEqual(sliced[1], (byte)0x01, "Sliced first element must be value 1"); // Slide 4 elements starting at index 2 and check sliced = span.Slice(2, 4); + Assert.AreEqual(sliced.Length, 4, "Sliced span lenght must be 4"); Assert.AreEqual(sliced[0], (byte)0x02, "Sliced first element must be value 2"); Assert.AreEqual(sliced[1], (byte)0x03, "Sliced first element must be value 3"); @@ -143,22 +155,27 @@ public void SliceTests() // Slide starting 4 at element check sliced = span.Slice(4); + Assert.AreEqual(sliced.Length, 12, "Sliced span lenght must be 12"); + for (int i = 0; i < sliced.Length; i++) { Assert.AreEqual(sliced[i], span[i + 4], "SpanByte value should be the same as from the original span"); } // Slice of slice - var secondSliced = sliced.Slice(2, 4); + Span secondSliced = sliced.Slice(2, 4); + Assert.AreEqual(secondSliced.Length, 4, "Sliced span lenght must be 12"); + for (int i = 0; i < secondSliced.Length; i++) { Assert.AreEqual(secondSliced[i], sliced[i + 2], "SpanByte value should be the same as from the original span"); } // Should be an empty one - var empty = span.Slice(span.Length); + Span empty = span.Slice(span.Length); + Assert.AreEqual(empty.Length, 0, "slicing all the span should result in an empty span"); Assert.IsTrue(empty.IsEmpty, "Empty span should be empty"); } @@ -167,39 +184,42 @@ public void SliceTests() public void CopyToTests() { byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; - SpanByte span = new SpanByte(array); + Span span = new Span(array); + // First a copy to with the full span - SpanByte toCopy = new byte[span.Length]; + Span toCopy = new Span(new byte[span.Length]); + span.CopyTo(toCopy); - for (int i = 0; i < span.Length; i++) - { - Assert.AreEqual(toCopy[i], span[i], "SpanByte value should be the same as from the original array"); - } + + CollectionAssert.AreEqual(array, toCopy.ToArray(), "Original array and SpanByte.CopyTo should be the same"); // Now create a larger span - toCopy = new byte[span.Length + 1]; + toCopy = new Span(new byte[span.Length + 1]); span.CopyTo(toCopy); - for (int i = 0; i < span.Length; i++) - { - Assert.AreEqual(toCopy[i], span[i], "SpanByte value should be the same as from the original array"); - } - Assert.AreEqual(toCopy[span.Length], (byte)0); + Assert.AreEqual(toCopy.Length, span.Length + 1); + + byte[] tempArray = new byte[span.Length + 1]; + Array.Copy(array, tempArray, array.Length); + + CollectionAssert.AreEqual(tempArray, toCopy.ToArray(), "Original array and SpanByte.CopyTo should be the same with larger destination"); + + Assert.AreEqual(toCopy[toCopy.Length - 1], (byte)0, "Last byte should be 0 (byte default)"); } [TestMethod] public void GetElementsTests() { - // Span from normal array + // Spanfrom normal array byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; - SpanByte span = new SpanByte(array); + Span span = new Span(array); for (int i = 0; i < span.Length; i++) { Assert.AreEqual(span[i], array[i], "SpanByte value should be the same as from the original array"); } // Partial span - span = new SpanByte(array, 2, 8); + span = new Span(array, 2, 8); for (int i = 0; i < span.Length; i++) { Assert.AreEqual(span[i], array[i + 2], "SpanByte value should be the same as from the original array"); @@ -210,11 +230,13 @@ public void GetElementsTests() public void SetElementsTests() { // Create a span, and set the data - SpanByte span = new byte[12]; + Span span = new Span(new byte[12]); + // All should be 0 for (int i = 0; i < span.Length; i++) { Assert.AreEqual(span[i], (byte)0, "SpanByte value should be 0"); + // Set a value span[i] = (byte)i; } diff --git a/nanoFramework.CoreLibrary/CoreLibrary.nfproj b/nanoFramework.CoreLibrary/CoreLibrary.nfproj index c44da984..cf5e4739 100644 --- a/nanoFramework.CoreLibrary/CoreLibrary.nfproj +++ b/nanoFramework.CoreLibrary/CoreLibrary.nfproj @@ -66,6 +66,13 @@ + + + + + + + @@ -188,7 +195,9 @@ - + + + @@ -251,4 +260,4 @@ - + \ No newline at end of file diff --git a/nanoFramework.CoreLibrary/System/Array.cs b/nanoFramework.CoreLibrary/System/Array.cs index 1256a436..86d93f12 100644 --- a/nanoFramework.CoreLibrary/System/Array.cs +++ b/nanoFramework.CoreLibrary/System/Array.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; @@ -343,6 +343,25 @@ public static int IndexOf(Array array, Object value, int startIndex, int count) return -1; } +#if NANOCLR_REFLECTION + /// + /// Creates and returns an empty array of the specified type. + /// + /// + /// + public static T[] Empty() + { + return EmptyArray.Value; + } + + private static class EmptyArray + { +#pragma warning disable CA1825, IDE0300 // this is the implementation of Array.Empty() + internal static readonly T[] Value = new T[0]; +#pragma warning restore CA1825, IDE0300 + } +#endif + [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool TrySzIndexOf(Array sourceArray, int sourceIndex, int count, Object value, out int retVal); diff --git a/nanoFramework.CoreLibrary/System/ArrayTypeMismatchException.cs b/nanoFramework.CoreLibrary/System/ArrayTypeMismatchException.cs new file mode 100644 index 00000000..658300b6 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/ArrayTypeMismatchException.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System +{ + /// + /// The exception that is thrown when an attempt is made to store an element of the wrong type within an array. + /// + [Serializable] + public class ArrayTypeMismatchException : SystemException + { + /// + /// Initializes a new instance of the class. + /// + public ArrayTypeMismatchException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// + public ArrayTypeMismatchException(string? message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// + /// + public ArrayTypeMismatchException(string? message, Exception? innerException) + : base(message, innerException) + { + } + } +} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs new file mode 100644 index 00000000..ff051537 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs @@ -0,0 +1,129 @@ +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Diagnostics.CodeAnalysis; +//using System.Runtime.CompilerServices; + +//namespace System.Collections.Generic +//{ +// [Serializable] +// public abstract partial class Comparer : IComparer, IComparer +// { +// // public static Comparer Default is runtime-specific + +// //public static Comparer Create(Comparison comparison) +// //{ +// // ArgumentNullException.ThrowIfNull(comparison); + +// // return new ComparisonComparer(comparison); +// //} + +// public abstract int Compare(T? x, T? y); + +// int IComparer.Compare(object? x, object? y) +// { +// if (x == null) return y == null ? 0 : -1; +// if (y == null) return 1; +// if (x is T && y is T) return Compare((T)x, (T)y); + +// throw new ArgumentException(); +// } +// } + +// internal sealed class ComparisonComparer : Comparer +// { +// //private readonly Comparison _comparison; + +// //public ComparisonComparer(Comparison comparison) +// //{ +// // _comparison = comparison; +// //} + +// public override int Compare(T? x, T? y) => _comparison(x!, y!); +// } + +// // Note: although there is a lot of shared code in the following +// // comparers, we do not incorporate it into a base class for perf +// // reasons. Adding another base class (even one with no fields) +// // means another generic instantiation, which can be costly esp. +// // for value types. +// [Serializable] +// // Needs to be public to support binary serialization compatibility +// public sealed partial class GenericComparer : Comparer where T : IComparable? +// { +// public override int Compare(T? x, T? y) +// { +// if (x != null) +// { +// if (y != null) return x.CompareTo(y); +// return 1; +// } +// if (y != null) return -1; +// return 0; +// } + +// // Equals method for the comparer itself. +// public override bool Equals([NotNullWhen(true)] object? obj) => +// obj != null && GetType() == obj.GetType(); + +// public override int GetHashCode() => +// GetType().GetHashCode(); +// } + +// [Serializable] +// // Needs to be public to support binary serialization compatibility +// public sealed class NullableComparer : Comparer +// { +// public NullableComparer() { } + +// public override int Compare(T? x, T? y) +// { +// if (x.HasValue) +// { +// if (y.HasValue) return Comparer.Default.Compare(x.value, y.value); +// return 1; +// } +// if (y.HasValue) return -1; +// return 0; +// } + +// // Equals method for the comparer itself. +// public override bool Equals([NotNullWhen(true)] object? obj) => +// obj != null && GetType() == obj.GetType(); + +// public override int GetHashCode() => +// GetType().GetHashCode(); +// } + +// [Serializable] +// // Needs to be public to support binary serialization compatibility +// public sealed partial class ObjectComparer : Comparer +// { +// public override int Compare(T? x, T? y) +// { +// return Comparer.Default.Compare(x, y); +// } + +// // Equals method for the comparer itself. +// public override bool Equals([NotNullWhen(true)] object? obj) => +// obj != null && GetType() == obj.GetType(); + +// public override int GetHashCode() => +// GetType().GetHashCode(); +// } + +// [Serializable] +// internal sealed partial class EnumComparer : Comparer +// { +// public EnumComparer() { } + +// // public override int Compare(T x, T y) is runtime-specific + +// // Equals method for the comparer itself. +// public override bool Equals([NotNullWhen(true)] object? obj) => +// obj != null && GetType() == obj.GetType(); + +// public override int GetHashCode() => +// GetType().GetHashCode(); +// } +//} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs new file mode 100644 index 00000000..a4fcece9 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs @@ -0,0 +1,93 @@ +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Diagnostics; +//using static System.RuntimeTypeHandle; + +//namespace System.Collections.Generic +//{ +// /// +// /// Helper class for creating the default and . +// /// +// /// +// /// This class is intentionally type-unsafe and non-generic to minimize the generic instantiation overhead of creating +// /// the default comparer/equality comparer for a new type parameter. Efficiency of the methods in here does not matter too +// /// much since they will only be run once per type parameter, but generic code involved in creating the comparers needs to be +// /// kept to a minimum. +// /// +// internal static class ComparerHelpers +// { +// /// +// /// Creates the default . +// /// +// /// The type to create the default comparer for. +// /// +// /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations, +// /// and in vm/jitinterface.cpp so the jit can model the behavior of this method. +// /// +// internal static object CreateDefaultComparer(Type type) +// { +// Debug.Assert(type != null && type is RuntimeType); + +// object? result = null; +// var runtimeType = (RuntimeType)type; + +// // If T implements IComparable return a GenericComparer +// if (typeof(IComparable<>).MakeGenericType(type).IsAssignableFrom(type)) +// { +// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer), runtimeType); +// } +// else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) +// { +// // Nullable does not implement IComparable directly because that would add an extra interface call per comparison. +// var embeddedType = (RuntimeType)type.GetGenericArguments()[0]; +// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer), embeddedType); +// } +// // The comparer for enums is specialized to avoid boxing. +// else if (type.IsEnum) +// { +// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumComparer<>), runtimeType); +// } + +// return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectComparer), runtimeType); +// } + +// ///// +// ///// Creates the default . +// ///// +// ///// The type to create the default equality comparer for. +// ///// +// ///// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations. +// ///// +// //internal static object CreateDefaultEqualityComparer(Type type) +// //{ +// // Debug.Assert(type != null && type is RuntimeType); + +// // object? result = null; +// // var runtimeType = (RuntimeType)type; + +// // if (type == typeof(string)) +// // { +// // return new StringEqualityComparer(); +// // } +// // else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type))) +// // { +// // // If T implements IEquatable return a GenericEqualityComparer +// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer), runtimeType); +// // } +// // else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) +// // { +// // // Nullable does not implement IEquatable directly because that would add an extra interface call per comparison. +// // var embeddedType = (RuntimeType)type.GetGenericArguments()[0]; +// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableEqualityComparer), embeddedType); +// // } +// // else if (type.IsEnum) +// // { +// // // The equality comparer for enums is specialized to avoid boxing. +// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<>), runtimeType); +// // } + +// // return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectEqualityComparer), runtimeType); +// //} +// } +//} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs new file mode 100644 index 00000000..ab1921c3 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs @@ -0,0 +1,22 @@ +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Runtime.CompilerServices; + +//namespace System.Collections.Generic +//{ +// public abstract partial class Comparer : IComparer, IComparer +// { +// // To minimize generic instantiation overhead of creating the comparer per type, we keep the generic portion of the code as small +// // as possible and define most of the creation logic in a non-generic class. +// public static Comparer Default { get; } = (Comparer)ComparerHelpers.CreateDefaultComparer(typeof(T)); +// } + +// internal sealed partial class EnumComparer : Comparer where T : struct, Enum +// { +// public override int Compare(T x, T y) +// { +// return RuntimeHelpers.EnumCompareTo(x, y); +// } +// } +//} diff --git a/nanoFramework.CoreLibrary/System/Diagnostics/StackTraceHiddenAttribute.cs b/nanoFramework.CoreLibrary/System/Diagnostics/StackTraceHiddenAttribute.cs new file mode 100644 index 00000000..7ad53368 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Diagnostics/StackTraceHiddenAttribute.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics +{ + /// + /// Types and Methods attributed with StackTraceHidden will be omitted from the stack trace text shown in StackTrace.ToString() + /// and Exception.StackTrace + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)] + public sealed class StackTraceHiddenAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public StackTraceHiddenAttribute() { } + } +} diff --git a/nanoFramework.CoreLibrary/System/Nullable.cs b/nanoFramework.CoreLibrary/System/Nullable.cs new file mode 100644 index 00000000..89f1dc56 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Nullable.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable CA1066 // Implement IEquatable when overriding Object.Equals + +namespace System +{ + // Because we have special type system support that says a boxed Nullable + // can be used where a boxed T is used, Nullable can not implement any interfaces + // at all (since T may not). + // + // Do NOT add any interfaces to Nullable! + + /// + /// Represents a value type that can be assigned . + /// + /// + [Serializable] + public partial struct Nullable where T : struct + { + // Do not rename (binary serialization) + private readonly bool hasValue; + // Do not rename (binary serialization) or make readonly (can be mutated in ToString, etc.) + internal T value; + + public Nullable(T value) + { + this.value = value; + hasValue = true; + } + + public readonly bool HasValue + { + get => hasValue; + } + + public readonly T Value + { + get + { + if (!hasValue) + { + throw new InvalidOperationException(); + } + return value; + } + } + + public readonly T GetValueOrDefault() => value; + + public readonly T GetValueOrDefault(T defaultValue) => + hasValue ? value : defaultValue; + + public override bool Equals(object? other) + { + if (!hasValue) + { + return other == null; + } + + if (other == null) + { + return false; + } + + return value.Equals(other); + } + + public override int GetHashCode() => hasValue ? value.GetHashCode() : 0; + + public override string? ToString() => hasValue ? value.ToString() : ""; + + public static implicit operator T?(T value) => + new T?(value); + + public static explicit operator T(T? value) => value!.Value; + } + + public static class Nullable + { + //public static int Compare(T? n1, T? n2) where T : struct + //{ + // if (n1.HasValue) + // { + // if (n2.HasValue) return Comparer.Default.Compare(n1.value, n2.value); + // return 1; + // } + // if (n2.HasValue) return -1; + // return 0; + //} + + //public static bool Equals(T? n1, T? n2) where T : struct + //{ + // if (n1.HasValue) + // { + // if (n2.HasValue) return EqualityComparer.Default.Equals(n1.value, n2.value); + // return false; + // } + // if (n2.HasValue) return false; + // return true; + //} + + // If the type provided is not a Nullable Type, return null. + // Otherwise, return the underlying type of the Nullable type + public static Type? GetUnderlyingType(Type nullableType) + { + ArgumentNullException.ThrowIfNull(nullableType); + + if (nullableType.IsGenericType && !nullableType.IsGenericTypeDefinition) + { + // Instantiated generic type only + Type genericType = nullableType.GetGenericTypeDefinition(); + if (ReferenceEquals(genericType, typeof(Nullable<>))) + { + return nullableType.GetGenericArguments()[0]; + } + } + return null; + } + + /// + /// Retrieves a readonly reference to the location in the instance where the value is stored. + /// + /// The underlying value type of the generic type. + /// The readonly reference to the input value. + /// A readonly reference to the location where the instance's value is stored. If the instance's is false, the current value at that location may be the default value. + /// + /// As the returned readonly reference refers to data that is stored in the input value, this method should only ever be + /// called when the input reference points to a value with an actual location and not an "rvalue" (an expression that may appear on the right side but not left side of an assignment). That is, if this API is called and the input reference + /// points to a value that is produced by the compiler as a defensive copy or a temporary copy, the behavior might not match the desired one. + /// + public static ref readonly T GetValueRefOrDefaultRef(ref readonly T? nullable) + where T : struct + { + return ref nullable.value; + } + } +} diff --git a/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs b/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs new file mode 100644 index 00000000..4d7def65 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one +#nullable enable + +namespace System +{ + /// + /// Provides a type-safe and memory-safe read-only representation of a contiguous region of arbitrary memory. + /// + /// The type of items in the . + [DebuggerTypeProxy(typeof(SpanDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] + public readonly ref struct ReadOnlySpan + { + private readonly T[] _array; + private readonly int _length; + + /// + /// Creates a new over the entirety of a specified array. + /// + /// The array from which to create the . + /// If the array is , this constructor returns a null . + public ReadOnlySpan(T[]? array) + { + _array = array ?? Array.Empty(); + _length = _array.Length; + } + + /// + /// Creates a new that includes a specified number of elements of an array starting at a specified index. + /// + /// The array from which to create the . + /// The zero-based index of the first element to include. + /// The number of elements to include. + /// This method returns when is . + /// Returns default when is null. + /// + /// + /// is , but or is non-zero. + /// + /// + /// -or- + /// + /// + /// is outside the bounds of the array. + /// + /// + /// -or- + /// + /// + /// and exceeds the number of elements in the array. + /// + /// + public ReadOnlySpan(T[]? array, int start, int length) + { + if (array == null) + { + if (start != 0 || length != 0) + { + throw new ArgumentOutOfRangeException(); + } + + _array = Array.Empty(); + _length = 0; + + return; + } + + if ((uint)start > (uint)array.Length + || (uint)length > (uint)(array.Length - start)) + { + throw new ArgumentOutOfRangeException(); + } + + NativeReadOnlySpanConstructor( + array, + start, + length); + } + + /// + /// Returns the specified element of the . + /// + /// The zero-based index. + /// + /// + /// Thrown when index less than 0 or index greater than or equal to Length + /// + public ref readonly T this[int index] + { + get + { + if ((uint)index >= (uint)_length) + { + throw new ArgumentOutOfRangeException(); + } + + return ref _array[index]; + } + } + + /// + /// The number of items in the read-only span. + /// + public int Length => _length; + + /// + /// Returns a value that indicates the current read-only span is empty. + /// + /// when the current span is empty; otherwise, . + public bool IsEmpty => _length == 0; + + /// + /// Returns a value that indicates whether two instances are not equal. + /// + /// The first read-only span to compare. + /// The second read-only span to compare. + /// + /// if the two instances are not equal; otherwise, . + /// + /// + /// Two instances are not equal if they have different lengths or if the corresponding elements of and point to different memory locations. + /// + public static bool operator !=(ReadOnlySpan left, ReadOnlySpan right) => !(left == right); + + /// + /// Defines an implicit conversion of an array to a + /// + public static implicit operator ReadOnlySpan(T[]? array) => new ReadOnlySpan(array); + + /// + /// Returns a value that indicates whether two instances are equal. + /// + /// The first span to compare. + /// The second span to compare. + /// + /// if the two instances are equal; otherwise, . + /// + /// + /// This tests if two instances are equal by comparing their lengths and the elements they point to. Two instances point to the same starting memory location, and have the same values. This does not compare the contents of two instances. + /// + public static bool operator ==(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + + for (int i = 0; i < left.Length; i++) + { + if (!Equals(left[i], right[i])) + { + return false; + } + } + + return true; + } + + /// + /// Copies the contents of this read-only span into a new array. + /// + /// + /// An array containing the data in the current span. + /// + /// + /// This method performs a heap allocation and data copy, and therefore, should be avoided if possible. It it sometimes necessary when APIs that support are not available, but APIs that work with arrays are. + /// + public T[] ToArray() + { + T[] destination = new T[_length]; + Array.Copy(_array, 0, destination, 0, _length); + return destination; + } + + #region native methods + + [MethodImpl(MethodImplOptions.InternalCall)] + private extern void NativeReadOnlySpanConstructor( + T[] array, + int start, + int length); + + #endregion + } +} + +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one diff --git a/nanoFramework.CoreLibrary/System/Runtime/InteropServices/InAttribute .cs b/nanoFramework.CoreLibrary/System/Runtime/InteropServices/InAttribute .cs new file mode 100644 index 00000000..fa82a88a --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Runtime/InteropServices/InAttribute .cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// Specifies that a parameter is passed to a method by reference, but is not modified by the method. + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class InAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public InAttribute() + { + } + } +} diff --git a/nanoFramework.CoreLibrary/System/Span.cs b/nanoFramework.CoreLibrary/System/Span.cs new file mode 100644 index 00000000..2a2670e1 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Span.cs @@ -0,0 +1,320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one +#nullable enable + +namespace System +{ + /// + /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed + /// or native memory, or to memory allocated on the stack. It is type-safe and memory-safe. + /// + /// The type of items in the . + [DebuggerTypeProxy(typeof(SpanDebugView<>))] + [DebuggerDisplay("{ToString(),raw}")] + public readonly ref struct Span + { + private readonly T[] _array; + private readonly int _length; + + /// + /// + /// Creates a new object over the entirety of a specified array. + /// + /// + /// The array from which to create the object. + /// If array is , this constructor returns a . + /// is a reference type, and is not an array of type . + public Span(T[]? array) + { + if (array == null) + { + this = default; + return; + } + + if (!typeof(T).IsValueType && array.GetType() != typeof(T[])) + { + throw new ArrayTypeMismatchException(); + } + + _array = array ?? []; + _length = _array.Length; + } + + /// + /// Creates a new object that includes a specified number of elements of an array starting at a specified index. + /// + /// The source array. + /// The zero-based index of the first element to include in the new . + /// The number of elements to include in the new . + /// This method returns when is . + /// is a reference type, and is not an array of type . + /// + /// + /// is , but or is non-zero. + /// + /// + /// -or- + /// + /// + /// is outside the bounds of the array. + /// + /// + /// -or- + /// + /// + /// and exceeds the number of elements in the array. + /// + /// + public Span( + T[]? array, + int start, + int length) + { + if (array == null) + { + if (start != 0 + || length != 0) + { + throw new ArgumentOutOfRangeException(); + } + + this = default; + return; + } + + if (!typeof(T).IsValueType + && array.GetType() != typeof(T[])) + { + throw new ArrayTypeMismatchException(); + } + + if ((uint)start > (uint)array.Length + || (uint)length > (uint)(array.Length - start)) + { + throw new ArgumentOutOfRangeException(); + } + + NativeSpanConstructor( + array, + start, + length); + } + + /// + /// Returns a reference to specified element of the span. + /// + /// The zero-based index. + /// + /// + /// Thrown when less than 0 or greater than or equal to . + /// + public ref T this[int index] + { + get + { + if ((uint)index >= (uint)_length) + { + throw new ArgumentOutOfRangeException(); + } + + return ref _array[index]; + } + } + + /// + /// Returns the length of the current span. + /// + public int Length => _length; + + /// + /// Returns a value that indicates whether the current is empty. + /// + /// if this span is empty; otherwise, . + public bool IsEmpty => _length == 0; + + /// + /// Returns a value that indicates whether two objects are not equal. + /// + /// The first span to compare. + /// The second span to compare. + /// + /// if the two objects are not equal; otherwise, . + /// + /// + /// Two objects are are not equal if they have different lengths or if the corresponding elements of and do not point to the same memory. + /// + public static bool operator !=(Span left, Span right) => !(left == right); + + /// + /// Returns an empty object. + /// + public static Span Empty => default; + + /// Gets an enumerator for this . + /// An enumerator for this span. + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Provides an enumerator for the elements of a . + /// + public ref struct Enumerator + { + /// The span being enumerated. + private readonly Span _span; + /// The next index to yield. + private int _index; + + /// Initialize the enumerator. + /// The span to enumerate. + internal Enumerator(Span span) + { + _span = span; + _index = -1; + } + + /// Advances the enumerator to the next item of the . + /// + /// if the enumerator successfully advanced to the next item; if the end of the span has been passed. + /// + public bool MoveNext() + { + int index = _index + 1; + + if (index < _span.Length) + { + _index = index; + return true; + } + + return false; + } + + /// Gets a reference to the item at the current position of the enumerator. + public ref T Current => ref _span[_index]; + } + + /// + /// Clears the contents of this object. + /// + /// + /// The method sets the items in the object to their default values. It does not remove items from the . + /// + public void Clear() + { + for (int i = 0; i < _length; i++) + { + _array[i] = default!; + } + } + + /// + /// Copies the contents of this into a destination . + /// + /// The destination object. + /// + /// is shorter than the source . + /// + /// + /// This method copies all of source to even if source and overlap. + /// + [MethodImpl(MethodImplOptions.InternalCall)] + public extern void CopyTo(Span destination); + + /// + /// Returns a value that indicates whether two objects are equal. + /// + /// The first span to compare. + /// The second span to compare. + /// + /// if the two objects are equal; otherwise, . + /// + /// + /// Two objects are equal if they have the same length and if the corresponding elements of and point to the same memory. + /// Note that the test for equality does *not* attempt to determine if the contents are equal. + /// + public static bool operator ==(Span left, Span right) => + left._length == right._length && left._array == right._array; + + /// + /// Defines an implicit conversion of a to a + /// + /// The object to convert to a . + /// + /// A read-only span that corresponds to the current instance. + /// + public static implicit operator ReadOnlySpan(Span span) => + new ReadOnlySpan(span._array, 0, span._length); + + /// + /// Forms a slice out of the current span that begins at a specified index. + /// + /// The zero-based index at which to begin the slice. + /// A span that consists of all elements of the current span from to the end of the span. + /// + /// is less than zero or greater than . + /// + public Span Slice(int start) + { + if ((uint)start > (uint)_length) + { + throw new ArgumentOutOfRangeException(); + } + + return new Span(_array, start, _length - start); + } + + /// + /// Forms a slice out of the current span starting at a specified index for a specified length. + /// + /// The zero-based index at which to begin the slice. + /// The desired length for the slice. + /// A span that consists of elements from the current span starting at . + /// + /// or + is less than zero or greater than . + /// + public Span Slice(int start, int length) + { + if ((uint)start > (uint)_length + || (uint)length > (uint)(_length - start)) + { + throw new ArgumentOutOfRangeException(); + } + + return new Span(_array, start, length); + } + + /// + /// Copies the contents of this span into a new array. + /// + /// + /// An array containing the data in the current span. + /// + /// + /// This method performs a heap allocation and therefore should be avoided if possible. Heap allocations are expected in APIs that work with arrays. Using such APIs is unavoidable if an alternative API overload that takes a does not exist. + /// + public T[] ToArray() + { + T[] destination = new T[_length]; + Array.Copy(_array, 0, destination, 0, _length); + return destination; + } + + #region native methods + + [MethodImpl(MethodImplOptions.InternalCall)] + private extern void NativeSpanConstructor( + T[] array, + int start, + int length); + + #endregion + } +} + +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one diff --git a/nanoFramework.CoreLibrary/System/SpanDebugView.cs b/nanoFramework.CoreLibrary/System/SpanDebugView.cs new file mode 100644 index 00000000..03c34234 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/SpanDebugView.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System +{ + [ExcludeType] + internal sealed class SpanDebugView + { + private readonly T[] _array; + + public SpanDebugView(Span span) + { + _array = span.ToArray(); + } + + public SpanDebugView(ReadOnlySpan span) + { + _array = span.ToArray(); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items => _array; + } +} diff --git a/nanoFramework.CoreLibrary/System/Type.cs b/nanoFramework.CoreLibrary/System/Type.cs index d7a134f5..e37e067d 100644 --- a/nanoFramework.CoreLibrary/System/Type.cs +++ b/nanoFramework.CoreLibrary/System/Type.cs @@ -51,6 +51,20 @@ public static Type GetType(String typeName) return GetTypeInternal(name, assemblyName, fVersion, ver); } + /// + /// Returns a object that represents a generic type definition from which the current generic type can be constructed. + /// + /// A object representing a generic type from which the current type can be constructed. + [MethodImpl(MethodImplOptions.InternalCall)] + public virtual extern Type GetGenericTypeDefinition(); + + /// + /// Returns an array of objects that represent the type arguments of a closed generic type or the type parameters of a generic type definition. + /// + /// An array of objects that represent the type arguments of a generic type. Returns an empty array if the current type is not a generic type. + [MethodImpl(MethodImplOptions.InternalCall)] + public extern Type[] GetGenericArguments(); + [Diagnostics.DebuggerHidden] [MethodImpl(MethodImplOptions.InternalCall)] private static extern Type GetTypeInternal(String typeName, string assemblyName, bool fVersion, int[] ver); @@ -357,6 +371,30 @@ public extern bool IsArray get; } + /// + /// Gets a value indicating whether the current type is a generic type. + /// + /// + /// if the current type is a generic type; otherwise, . + /// + public extern bool IsGenericType + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /// + /// Gets a value that indicates whether the type is a generic type definition. + /// + /// + /// if the current type is a generic type definition; otherwise, . + /// + public extern bool IsGenericTypeDefinition + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + /// /// When overridden in a derived class, returns the Type of the object encompassed or referred to by the current array, pointer or reference type. /// From 32a57c5f2a0b7811f13c15a053b5bd4ae3b02113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 18 Sep 2025 14:25:05 +0100 Subject: [PATCH 08/17] Add `ByRefLikeGenerics` to runtime features (#250) ***NO_CI*** --- .../Runtime/CompilerServices/RuntimeFeature.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeFeature.cs b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeFeature.cs index a5f408a5..8d1bdf58 100644 --- a/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeFeature.cs +++ b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeFeature.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Runtime.CompilerServices @@ -13,17 +13,22 @@ public static partial class RuntimeFeature /// public const string DefaultImplementationsOfInterfaces = nameof(DefaultImplementationsOfInterfaces); + /// + /// Represents a runtime feature where byref-like types can be used in Generic parameters. + /// + public const string ByRefLikeGenerics = nameof(ByRefLikeGenerics); + /// /// Checks whether a certain feature is supported by the Runtime. /// public static bool IsSupported(string feature) { - if (feature == DefaultImplementationsOfInterfaces) + return feature switch { - return true; - } - - return false; + ByRefLikeGenerics or + DefaultImplementationsOfInterfaces => true, + _ => false, + }; } } } From ab7909fdc0693bf4d03760d46e0aadcfa632a30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 18 Sep 2025 20:57:05 +0100 Subject: [PATCH 09/17] Remove exclusion of `DebuggerTypeProxyAttribute` (#251) ***NO_CI*** --- .../System/Diagnostics/DebuggerAttributes.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nanoFramework.CoreLibrary/System/Diagnostics/DebuggerAttributes.cs b/nanoFramework.CoreLibrary/System/Diagnostics/DebuggerAttributes.cs index fc80ca23..1b2d8925 100644 --- a/nanoFramework.CoreLibrary/System/Diagnostics/DebuggerAttributes.cs +++ b/nanoFramework.CoreLibrary/System/Diagnostics/DebuggerAttributes.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; @@ -244,7 +244,6 @@ public DebuggerBrowsableState State /// /// Available only in mscorlib build with support for System.Reflection. [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] - [ExcludeType] public sealed class DebuggerTypeProxyAttribute : Attribute { private readonly string _typeName; From e99e3aa71880c0ff89871b98d4cd8e22bdea1d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Sep 2025 17:23:37 +0100 Subject: [PATCH 10/17] Add `IsReferenceOrContainsReferences` runtime helper (#252) ***NO_CI*** --- .../NFUnitTestSystemLib.nfproj | 4 +- .../RuntimeHelpersTests.cs | 68 +++++++++++++++++++ .../CompilerServices/RuntimeHelpers.cs | 19 +++++- 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs diff --git a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj index d3cc5036..95da98f9 100644 --- a/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj +++ b/Tests/NFUnitTestSystemLib/NFUnitTestSystemLib.nfproj @@ -21,9 +21,11 @@ true UnitTest v1.0 + 13.0 + @@ -62,4 +64,4 @@ - \ No newline at end of file + diff --git a/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs new file mode 100644 index 00000000..1824ab0f --- /dev/null +++ b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using nanoFramework.TestFramework; + +namespace NFUnitTestSystemLib +{ + [TestClass] + class RuntimeHelpersTests + { + [TestMethod] + public static void IsReferenceOrContainsReferences() + { + Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsFalse(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>()); + //Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + } + + private struct StructWithoutReferences + { + public int a, b, c; + } + + private struct StructWithReferences + { + public int a, b, c; + public object d; + } + + private ref struct RefStructWithoutReferences + { + public int a; + public long b; + } + + private ref struct RefStructWithReferences + { + public int a; + public object b; + } + + // TODO: add after checking viability of ref fields in ref structs + //private ref struct RefStructWithRef + //{ + // public ref int a; + + // internal RefStructWithRef(ref int aVal) + // { + // a = ref aVal; + // } + //} + + private ref struct RefStructWithNestedRef + { + public Span a; + } + } +} diff --git a/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeHelpers.cs b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeHelpers.cs index e306876e..7862f63d 100644 --- a/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Runtime.CompilerServices { +#pragma warning disable S4200 // Native methods should be wrapped + /// /// Provides a set of static methods and properties that provide support for compilers. This class cannot be inherited. /// @@ -43,5 +45,20 @@ public static extern int OffsetToStringData [MethodImpl(MethodImplOptions.InternalCall)] get; } + +#if NANOCLR_REFLECTION + + /// + /// Returns a value that indicates whether the specified type is a reference type or a value type that contains references or by-refs. + /// + /// The type. + /// if the given type is a reference type or a value type that contains references or by-refs; otherwise, . + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern bool IsReferenceOrContainsReferences() where T : allows ref struct; + +#endif + } + +#pragma warning restore S4200 // Native methods should be wrapped } From 03e6ddb16fe1af9c2687ab918bbfd3d4b914e921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Sep 2025 18:13:49 +0100 Subject: [PATCH 11/17] Work CD-CI - Add AZDO template to run unit tests with specific build of nanoCLR. ***NO_CI*** --- .../check-nf-interpreter-to-test.yml | 74 +++++++++++++++++++ azure-pipelines.yml | 2 + 2 files changed, 76 insertions(+) create mode 100644 azure-pipelines-templates/check-nf-interpreter-to-test.yml diff --git a/azure-pipelines-templates/check-nf-interpreter-to-test.yml b/azure-pipelines-templates/check-nf-interpreter-to-test.yml new file mode 100644 index 00000000..9fdf8513 --- /dev/null +++ b/azure-pipelines-templates/check-nf-interpreter-to-test.yml @@ -0,0 +1,74 @@ +# Copyright (c) .NET Foundation and Contributors +# See LICENSE file in the project root for full license information. + +steps: + - task: PowerShell@2 + displayName: Check nf-interpreter to test + condition: ne(variables['System.PullRequest.PullRequestNumber'], '') + inputs: + failOnStderr: false + targetType: "inline" + script: | + + # compute authorization header in format "AUTHORIZATION: basic 'encoded token'" + # 'encoded token' is the Base64 of the string "nfbot:personal-token" + $auth = "basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("nfbot:$(GitHubToken)")))" + + # find PR + "Getting PR #$env:System_PullRequest_PullRequestNumber details..." | Write-Host -ForegroundColor White -NoNewline + $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$env:Build_Repository_Name/pulls/$env:System_PullRequest_PullRequestNumber" -Header @{"Authorization"="$auth"} -ContentType "application/json" -Method GET + + if($($pr.number) -eq "$env:System_PullRequest_PullRequestNumber") + { + 'OK' | Write-Host -ForegroundColor Green + } + + # grab PR commit message + $prCommitMessage = $($pr.body) + + # look for test prompt in PR commit message + # pattern is "[tested against nanoclr buildId NNN]" + + if($prCommitMessage -match "\[tested against nanoclr buildId (\d+)\]") + { + $buildId = $matches[1] + "AZDO build ID found: $buildId" | Write-Host -ForegroundColor White + + echo "##vso[task.setvariable variable=NFINTERPRETER_BUILDID]$buildId" + } + else + { + "No build ID found" | Write-Host -ForegroundColor Red + } + env: + GITHUB_TOKEN: $(GitHubToken) + + - task: DownloadPipelineArtifact@2 + condition: >- + and( + eq(variables['DownloadNanoClrPreview'], true), + ne(variables['NFINTERPRETER_BUILDID'], '') + ) + displayName: Download nanoCLR preview + inputs: + buildType: specific + project: 'nf-interpreter' + definition: '34' + buildVersionToDownload: specific + allowFailedBuilds: true + pipelineId: $(NFINTERPRETER_BUILDID) + artifactName: 'nanoclr_cli' + targetPath: '$(Pipeline.Workspace)/nanoclr' + + - task: PowerShell@2 + condition: >- + and( + succeeded(), + eq(variables['DownloadNanoClrPreview'], true), + ne(variables['NFINTERPRETER_BUILDID'], '') + ) + displayName: Set nanoCLR preview path + inputs: + targetType: 'inline' + script: | + Write-Host "##vso[task.setvariable variable=NF_MDP_NANOCLR_INSTANCE_PATH]$(Pipeline.Workspace)/nanoclr/nanoFramework.nanoCLR.dll" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fbfe4dfd..440fdca7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,6 +69,8 @@ jobs: - template: azure-pipelines-templates/process-pr-labels.yml@templates + - template: azure-pipelines-templates/check-nf-interpreter-to-test.yml + # build steps only - template: azure-pipelines-templates/class-lib-build-only.yml@templates parameters: From 412684acb2a090e904e9d88af8dbb3c72a070d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Sep 2025 18:45:43 +0100 Subject: [PATCH 12/17] Add collection related interfaces and classes (#253) ***NO_CI*** --- nanoFramework.CoreLibrary/CoreLibrary.nfproj | 8 +- .../System/Array.Enumerators.cs | 101 ++++++ .../System/Collections/Generic/Comparer.cs | 312 ++++++++++-------- .../Collections/Generic/ComparerHelpers.cs | 93 ------ .../System/Collections/Generic/IComparer.cs | 40 +-- .../System/Collections/Generic/IEnumerable.cs | 19 ++ .../System/Collections/Generic/IEnumerator.cs | 30 ++ .../System/Collections/IEnumerable.cs | 4 +- .../System/Collections/IList.cs | 24 +- .../System/IComparable.cs | 44 ++- 10 files changed, 414 insertions(+), 261 deletions(-) create mode 100644 nanoFramework.CoreLibrary/System/Array.Enumerators.cs delete mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs create mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerable.cs create mode 100644 nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerator.cs diff --git a/nanoFramework.CoreLibrary/CoreLibrary.nfproj b/nanoFramework.CoreLibrary/CoreLibrary.nfproj index cf5e4739..e1501725 100644 --- a/nanoFramework.CoreLibrary/CoreLibrary.nfproj +++ b/nanoFramework.CoreLibrary/CoreLibrary.nfproj @@ -38,7 +38,7 @@ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true true - default + 13.0 true @@ -66,10 +66,12 @@ + - + + @@ -260,4 +262,4 @@ - \ No newline at end of file + diff --git a/nanoFramework.CoreLibrary/System/Array.Enumerators.cs b/nanoFramework.CoreLibrary/System/Array.Enumerators.cs new file mode 100644 index 00000000..532e4483 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Array.Enumerators.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace System +{ + internal abstract class SZGenericArrayEnumeratorBase : IDisposable + { + protected int _index; + protected readonly int _endIndex; + + protected SZGenericArrayEnumeratorBase(int endIndex) + { + _index = -1; + _endIndex = endIndex; + } + + public bool MoveNext() + { + int index = _index + 1; + + if ((uint)index < (uint)_endIndex) + { + _index = index; + + return true; + } + + _index = _endIndex; + + return false; + } + + public void Reset() => _index = -1; + + public void Dispose() + { + } + } + + internal sealed class SZGenericArrayEnumerator : SZGenericArrayEnumeratorBase, IEnumerator + { + private readonly T[]? _array; + + /// Provides an empty enumerator singleton. + /// + /// If the consumer is using SZGenericArrayEnumerator elsewhere or is otherwise likely + /// to be using T[] elsewhere, this singleton should be used. Otherwise, GenericEmptyEnumerator's + /// singleton should be used instead, as it doesn't reference T[] in order to reduce footprint. + /// + internal static readonly SZGenericArrayEnumerator Empty = new SZGenericArrayEnumerator(null, 0); + + internal SZGenericArrayEnumerator(T[]? array, int endIndex) + : base(endIndex) + { + Debug.Assert(array == null || endIndex == array.Length); + _array = array; + } + + public T Current + { + get + { + if ((uint)_index >= (uint)_endIndex) + { + throw new InvalidOperationException(); + } + + return _array![_index]; + } + } + + object? IEnumerator.Current => Current; + } + + internal abstract class GenericEmptyEnumeratorBase : IDisposable, IEnumerator + { +#pragma warning disable CA1822 // https://github.com/dotnet/roslyn-analyzers/issues/5911 + public bool MoveNext() => false; + + public object Current + { + get + { + return default; + } + } + + public void Reset() { } + + public void Dispose() { } +#pragma warning restore CA1822 + } +} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs index ff051537..309edfb2 100644 --- a/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/Comparer.cs @@ -1,129 +1,183 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. - -//using System.Diagnostics.CodeAnalysis; -//using System.Runtime.CompilerServices; - -//namespace System.Collections.Generic -//{ -// [Serializable] -// public abstract partial class Comparer : IComparer, IComparer -// { -// // public static Comparer Default is runtime-specific - -// //public static Comparer Create(Comparison comparison) -// //{ -// // ArgumentNullException.ThrowIfNull(comparison); - -// // return new ComparisonComparer(comparison); -// //} - -// public abstract int Compare(T? x, T? y); - -// int IComparer.Compare(object? x, object? y) -// { -// if (x == null) return y == null ? 0 : -1; -// if (y == null) return 1; -// if (x is T && y is T) return Compare((T)x, (T)y); - -// throw new ArgumentException(); -// } -// } - -// internal sealed class ComparisonComparer : Comparer -// { -// //private readonly Comparison _comparison; - -// //public ComparisonComparer(Comparison comparison) -// //{ -// // _comparison = comparison; -// //} - -// public override int Compare(T? x, T? y) => _comparison(x!, y!); -// } - -// // Note: although there is a lot of shared code in the following -// // comparers, we do not incorporate it into a base class for perf -// // reasons. Adding another base class (even one with no fields) -// // means another generic instantiation, which can be costly esp. -// // for value types. -// [Serializable] -// // Needs to be public to support binary serialization compatibility -// public sealed partial class GenericComparer : Comparer where T : IComparable? -// { -// public override int Compare(T? x, T? y) -// { -// if (x != null) -// { -// if (y != null) return x.CompareTo(y); -// return 1; -// } -// if (y != null) return -1; -// return 0; -// } - -// // Equals method for the comparer itself. -// public override bool Equals([NotNullWhen(true)] object? obj) => -// obj != null && GetType() == obj.GetType(); - -// public override int GetHashCode() => -// GetType().GetHashCode(); -// } - -// [Serializable] -// // Needs to be public to support binary serialization compatibility -// public sealed class NullableComparer : Comparer -// { -// public NullableComparer() { } - -// public override int Compare(T? x, T? y) -// { -// if (x.HasValue) -// { -// if (y.HasValue) return Comparer.Default.Compare(x.value, y.value); -// return 1; -// } -// if (y.HasValue) return -1; -// return 0; -// } - -// // Equals method for the comparer itself. -// public override bool Equals([NotNullWhen(true)] object? obj) => -// obj != null && GetType() == obj.GetType(); - -// public override int GetHashCode() => -// GetType().GetHashCode(); -// } - -// [Serializable] -// // Needs to be public to support binary serialization compatibility -// public sealed partial class ObjectComparer : Comparer -// { -// public override int Compare(T? x, T? y) -// { -// return Comparer.Default.Compare(x, y); -// } - -// // Equals method for the comparer itself. -// public override bool Equals([NotNullWhen(true)] object? obj) => -// obj != null && GetType() == obj.GetType(); - -// public override int GetHashCode() => -// GetType().GetHashCode(); -// } - -// [Serializable] -// internal sealed partial class EnumComparer : Comparer -// { -// public EnumComparer() { } - -// // public override int Compare(T x, T y) is runtime-specific - -// // Equals method for the comparer itself. -// public override bool Equals([NotNullWhen(true)] object? obj) => -// obj != null && GetType() == obj.GetType(); - -// public override int GetHashCode() => -// GetType().GetHashCode(); -// } -//} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace System.Collections.Generic +{ + ///// + ///// Provides a base class for implementations of the IComparer{T} generic interface. + ///// + ///// The type of objects to compare. + //[Serializable] + //public abstract partial class Comparer : IComparer, IComparer + //{ + // protected Comparer() + // { + + // } + // //public static Comparer Create(Comparison comparison) + // //{ + // // ArgumentNullException.ThrowIfNull(comparison); + + // // return new ComparisonComparer(comparison); + // //} + + // /// + // /// Returns a default sort-order comparer for the type specified by the generic argument. + // /// + // /// + // /// An object that inherits {T} and serves as a sort-order comparer for type {T}. + // /// + // public static extern Comparer Default + // { + // [MethodImpl(MethodImplOptions.InternalCall)] + // get; + // } + + // public abstract int Compare(T? x, T? y); + + // int IComparer.Compare(object? x, object? y) + // { + // if (x == null) + // { + // return y == null ? 0 : -1; + // } + + // if (y == null) + // { + // return 1; + // } + + // if (x is T && y is T) + // { + // return Compare((T)x, (T)y); + // } + + // throw new ArgumentException(); + // } + //} + + //internal sealed class ComparisonComparer : Comparer + //{ + // private readonly Comparison _comparison; + + // public ComparisonComparer(Comparison comparison) + // { + // _comparison = comparison; + // } + + // public override int Compare(T? x, T? y) => _comparison(x!, y!); + //} + + // Note: although there is a lot of shared code in the following + // comparers, we do not incorporate it into a base class for perf + // reasons. Adding another base class (even one with no fields) + // means another generic instantiation, which can be costly esp. + // for value types. + //[Serializable] + // Needs to be public to support binary serialization compatibility + //public sealed partial class GenericComparer : Comparer where T : IComparable? + //{ + // public override int Compare(T? x, T? y) + // { + // if (x != null) + // { + // if (y != null) + // { + // return x.CompareTo(y); + // } + + // return 1; + // } + // if (y != null) + // { + // return -1; + // } + + // return 0; + // } + + // // Equals method for the comparer itself. + // public override bool Equals([NotNullWhen(true)] object? obj) => + // obj != null && GetType() == obj.GetType(); + + // public override int GetHashCode() => + // GetType().GetHashCode(); + //} + + ///// + ///// + ///// + ///// + //[Serializable] + //public sealed class NullableComparer : Comparer where T : struct + //{ + // public NullableComparer() { } + + // public override int Compare(T? x, T? y) + // { + // if (x.HasValue) + // { + // if (y.HasValue) + // { + // return Comparer.Default.Compare(x.value, y.value); + // } + + // return 1; + // } + + // if (y.HasValue) + // { + // return -1; + // } + + // return 0; + // } + + // // Equals method for the comparer itself. + // public override bool Equals([NotNullWhen(true)] object? obj) => + // obj != null && GetType() == obj.GetType(); + + // public override int GetHashCode() => + // GetType().GetHashCode(); + //} + + //[Serializable] + //public sealed partial class ObjectComparer : Comparer + //{ + // public override int Compare(T? x, T? y) + // { + // return Default.Compare(x, y); + // } + + // // Equals method for the comparer itself. + // public override bool Equals([NotNullWhen(true)] object? obj) => + // obj != null && GetType() == obj.GetType(); + + // public override int GetHashCode() => + // GetType().GetHashCode(); + //} + + //[Serializable] + //internal sealed partial class EnumComparer : Comparer where T : struct, Enum + //{ + // public EnumComparer() { } + + // public override int Compare(T x, T y) + // { + // // TODO: native? + // throw new NotImplementedException(); + // } + + // // Equals method for the comparer itself. + // public override bool Equals([NotNullWhen(true)] object? obj) => + // obj != null && GetType() == obj.GetType(); + + // public override int GetHashCode() => + // GetType().GetHashCode(); + //} +} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs deleted file mode 100644 index a4fcece9..00000000 --- a/nanoFramework.CoreLibrary/System/Collections/Generic/ComparerHelpers.cs +++ /dev/null @@ -1,93 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. - -//using System.Diagnostics; -//using static System.RuntimeTypeHandle; - -//namespace System.Collections.Generic -//{ -// /// -// /// Helper class for creating the default and . -// /// -// /// -// /// This class is intentionally type-unsafe and non-generic to minimize the generic instantiation overhead of creating -// /// the default comparer/equality comparer for a new type parameter. Efficiency of the methods in here does not matter too -// /// much since they will only be run once per type parameter, but generic code involved in creating the comparers needs to be -// /// kept to a minimum. -// /// -// internal static class ComparerHelpers -// { -// /// -// /// Creates the default . -// /// -// /// The type to create the default comparer for. -// /// -// /// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations, -// /// and in vm/jitinterface.cpp so the jit can model the behavior of this method. -// /// -// internal static object CreateDefaultComparer(Type type) -// { -// Debug.Assert(type != null && type is RuntimeType); - -// object? result = null; -// var runtimeType = (RuntimeType)type; - -// // If T implements IComparable return a GenericComparer -// if (typeof(IComparable<>).MakeGenericType(type).IsAssignableFrom(type)) -// { -// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer), runtimeType); -// } -// else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) -// { -// // Nullable does not implement IComparable directly because that would add an extra interface call per comparison. -// var embeddedType = (RuntimeType)type.GetGenericArguments()[0]; -// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer), embeddedType); -// } -// // The comparer for enums is specialized to avoid boxing. -// else if (type.IsEnum) -// { -// result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumComparer<>), runtimeType); -// } - -// return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectComparer), runtimeType); -// } - -// ///// -// ///// Creates the default . -// ///// -// ///// The type to create the default equality comparer for. -// ///// -// ///// The logic in this method is replicated in vm/compile.cpp to ensure that NGen saves the right instantiations. -// ///// -// //internal static object CreateDefaultEqualityComparer(Type type) -// //{ -// // Debug.Assert(type != null && type is RuntimeType); - -// // object? result = null; -// // var runtimeType = (RuntimeType)type; - -// // if (type == typeof(string)) -// // { -// // return new StringEqualityComparer(); -// // } -// // else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type))) -// // { -// // // If T implements IEquatable return a GenericEqualityComparer -// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer), runtimeType); -// // } -// // else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) -// // { -// // // Nullable does not implement IEquatable directly because that would add an extra interface call per comparison. -// // var embeddedType = (RuntimeType)type.GetGenericArguments()[0]; -// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableEqualityComparer), embeddedType); -// // } -// // else if (type.IsEnum) -// // { -// // // The equality comparer for enums is specialized to avoid boxing. -// // result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(EnumEqualityComparer<>), runtimeType); -// // } - -// // return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectEqualityComparer), runtimeType); -// //} -// } -//} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs index ab1921c3..dd78f6e0 100644 --- a/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/IComparer.cs @@ -1,22 +1,22 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -//using System.Runtime.CompilerServices; +#nullable enable -//namespace System.Collections.Generic -//{ -// public abstract partial class Comparer : IComparer, IComparer -// { -// // To minimize generic instantiation overhead of creating the comparer per type, we keep the generic portion of the code as small -// // as possible and define most of the creation logic in a non-generic class. -// public static Comparer Default { get; } = (Comparer)ComparerHelpers.CreateDefaultComparer(typeof(T)); -// } - -// internal sealed partial class EnumComparer : Comparer where T : struct, Enum -// { -// public override int Compare(T x, T y) -// { -// return RuntimeHelpers.EnumCompareTo(x, y); -// } -// } -//} +namespace System.Collections.Generic +{ + /// + /// Defines a method that a type implements to compare two objects. + /// + /// The type of objects to compare. This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. + public interface IComparer where T : allows ref struct + { + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// + int Compare(T? x, T? y); + } +} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerable.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerable.cs new file mode 100644 index 00000000..a09be45d --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerable.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + /// + /// Exposes the enumerator, which supports a simple iteration over a collection of a specified type. + /// + /// The type of objects to enumerate. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. + public interface IEnumerable : IEnumerable + where T : allows ref struct + { + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + new IEnumerator GetEnumerator(); + } +} diff --git a/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerator.cs b/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerator.cs new file mode 100644 index 00000000..bdb8cf16 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Collections/Generic/IEnumerator.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Collections.Generic +{ + /// + /// Supports a simple iteration over a generic collection. + /// + /// + /// The type of objects to enumerate. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. + /// + + public interface IEnumerator : IDisposable, IEnumerator + where T : allows ref struct + { + /// + /// Gets the element in the collection at the current position of the enumerator. + /// + /// The element in the collection at the current position of the enumerator. + new T Current + { + get; + } + + // NOTE: An implementation of an enumerator using a ref struct T will + // not be able to implement IEnumerator.Current to return that T (as + // doing so would require boxing). It should throw a NotSupportedException + // from that property implementation. + } +} diff --git a/nanoFramework.CoreLibrary/System/Collections/IEnumerable.cs b/nanoFramework.CoreLibrary/System/Collections/IEnumerable.cs index d66ecf55..f4aa575a 100644 --- a/nanoFramework.CoreLibrary/System/Collections/IEnumerable.cs +++ b/nanoFramework.CoreLibrary/System/Collections/IEnumerable.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Collections @@ -11,7 +11,7 @@ public interface IEnumerable /// /// Returns an enumerator that iterates through a collection. /// - /// An IEnumerator object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator GetEnumerator(); } } diff --git a/nanoFramework.CoreLibrary/System/Collections/IList.cs b/nanoFramework.CoreLibrary/System/Collections/IList.cs index 90e0f63b..ec6f72c7 100644 --- a/nanoFramework.CoreLibrary/System/Collections/IList.cs +++ b/nanoFramework.CoreLibrary/System/Collections/IList.cs @@ -1,10 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Collections { - using System; - /// /// Represents a non-generic collection of objects that can be individually accessed by index. /// @@ -27,16 +25,16 @@ object this[int index] /// /// Adds an item to the IList. /// - /// The object to add to the IList. + /// The object to add to the IList. /// The position into which the new element was inserted, or -1 to indicate that the item was not inserted into the collection. - int Add(Object value); + int Add(object item); /// /// Determines whether the IList contains a specific value. /// - /// The object to locate in the IList. + /// The object to locate in the IList. /// true if the Object is found in the IList; otherwise, false. - bool Contains(Object value); + bool Contains(object item); /// /// Removes all items from the IList. @@ -68,22 +66,22 @@ bool IsFixedSize /// /// Determines the index of a specific item in the IList. /// - /// The object to locate in the IList. + /// The object to locate in the IList. /// The index of value if found in the list; otherwise, -1. - int IndexOf(Object value); + int IndexOf(object item); /// /// Inserts an item to the IList at the specified index. /// /// The zero-based index at which value should be inserted. - /// The object to insert into the IList. - void Insert(int index, Object value); + /// The object to insert into the IList. + void Insert(int index, object item); /// /// Removes the first occurrence of a specific object from the IList. /// - /// The object to remove from the IList. - void Remove(Object value); + /// The object to remove from the IList. + void Remove(object item); /// /// Removes the IList item at the specified index. diff --git a/nanoFramework.CoreLibrary/System/IComparable.cs b/nanoFramework.CoreLibrary/System/IComparable.cs index f73543dd..27abf9b9 100644 --- a/nanoFramework.CoreLibrary/System/IComparable.cs +++ b/nanoFramework.CoreLibrary/System/IComparable.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System @@ -20,4 +20,46 @@ public interface IComparable int CompareTo(Object obj); } +#if NANOCLR_REFLECTION + +#nullable enable + + /// + /// Defines a generalized comparison method that a value type or class implements to create a type-specific comparison method for ordering or sorting its instances. + /// + /// + /// The type of object to compare. This type parameter is contravariant. That is, you can use either the type you specified or any type that is less derived. + /// + public interface IComparable where T : allows ref struct + { + // Interface does not need to be marked with the serializable attribute + + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// An object to compare with this instance. + /// + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: + /// + /// + /// Value + /// Meaning + /// + /// + /// Less than zero + /// This instance precedes in the sort order. + /// + /// + /// Zero + /// This instance occurs in the same position in the sort order as . + /// + /// + /// Greater than zero + /// This instance follows in the sort order. + /// + /// + /// + int CompareTo(T? other); + } + +#endif + } From 3cf3175c7f6e5a9547623bfefea037f90b8992a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Sep 2025 19:42:08 +0100 Subject: [PATCH 13/17] Add IntelliSense comments to `Nullable` classes (#254) ***NO_CI*** --- nanoFramework.CoreLibrary/System/Nullable.cs | 64 +++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/nanoFramework.CoreLibrary/System/Nullable.cs b/nanoFramework.CoreLibrary/System/Nullable.cs index 89f1dc56..e7c06098 100644 --- a/nanoFramework.CoreLibrary/System/Nullable.cs +++ b/nanoFramework.CoreLibrary/System/Nullable.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable CA1066 // Implement IEquatable when overriding Object.Equals +#nullable enable namespace System { @@ -14,26 +15,46 @@ namespace System /// /// Represents a value type that can be assigned . /// - /// + /// The underlying value type of the generic type. [Serializable] public partial struct Nullable where T : struct { // Do not rename (binary serialization) private readonly bool hasValue; // Do not rename (binary serialization) or make readonly (can be mutated in ToString, etc.) - internal T value; + internal T value; + /// + /// Initializes a new instance of the structure to the specified value. + /// + /// A value type. + /// + /// The constructor initializes the HasValue property of the new object to , and the Value property to the value of the parameter. + /// public Nullable(T value) { this.value = value; hasValue = true; } + /// + /// Gets a value indicating whether the current object has a valid value of its underlying type. + /// + /// + /// if the current object has a value; if the current object has no . + /// public readonly bool HasValue { get => hasValue; } + /// + /// Gets the value of the current object if it has been assigned a valid underlying value. + /// + /// + /// The value of the current object if the property is . An exception is thrown if the property is . + /// + /// The property is . public readonly T Value { get @@ -46,11 +67,27 @@ public readonly T Value } } + /// + /// Retrieves the value of the current object, or the default value of the underlying type. + /// + /// The value of the property if the property is ; otherwise, the default value of the underlying type. + /// + /// The method returns a value even if the property is (unlike the property, which throws an exception). If the property is , the method returns the default value of the underlying type. + /// public readonly T GetValueOrDefault() => value; + /// + /// Retrieves the value of the current object, or the specified default value. + /// + /// A value to return if the property is . + /// The value of the property if the property is ; otherwise, the default value of the underlying type. + /// + /// The method returns a value even if the property is (unlike the property, which throws an exception). If the property is , the method returns the default value of the underlying type. + /// public readonly T GetValueOrDefault(T defaultValue) => hasValue ? value : defaultValue; + /// public override bool Equals(object? other) { if (!hasValue) @@ -66,16 +103,31 @@ public override bool Equals(object? other) return value.Equals(other); } + /// public override int GetHashCode() => hasValue ? value.GetHashCode() : 0; + /// public override string? ToString() => hasValue ? value.ToString() : ""; + /// + /// Creates a new object initialized to a specified value. + /// + /// A value type. + /// A object whose property is initialized with the value parameter. public static implicit operator T?(T value) => new T?(value); + /// + /// Defines an explicit conversion of a instance to its underlying value. + /// + /// A nullable value. + /// The value of the property of the parameter. public static explicit operator T(T? value) => value!.Value; } + /// + /// Supports a value type that can be assigned . This class cannot be inherited. + /// public static class Nullable { //public static int Compare(T? n1, T? n2) where T : struct @@ -100,6 +152,12 @@ public static class Nullable // return true; //} + /// + /// Returns the underlying type argument of the specified nullable type. + /// + /// A object that describes a closed generic nullable type. + /// The type argument of the parameter, if the parameter is a closed generic nullable type; otherwise, . + /// is . // If the type provided is not a Nullable Type, return null. // Otherwise, return the underlying type of the Nullable type public static Type? GetUnderlyingType(Type nullableType) @@ -110,6 +168,7 @@ public static class Nullable { // Instantiated generic type only Type genericType = nullableType.GetGenericTypeDefinition(); + if (ReferenceEquals(genericType, typeof(Nullable<>))) { return nullableType.GetGenericArguments()[0]; @@ -127,6 +186,7 @@ public static class Nullable /// /// As the returned readonly reference refers to data that is stored in the input value, this method should only ever be /// called when the input reference points to a value with an actual location and not an "rvalue" (an expression that may appear on the right side but not left side of an assignment). That is, if this API is called and the input reference + /// called when the input reference points to a value with an actual location and not an "rvalue" (an expression that may appear on the right side but not left side of an assignment). That is, if this API is called and the input reference /// points to a value that is produced by the compiler as a defensive copy or a temporary copy, the behavior might not match the desired one. /// public static ref readonly T GetValueRefOrDefaultRef(ref readonly T? nullable) From 5d24cd3f058a777b9175f75dd8a390601b77886c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 24 Sep 2025 19:43:32 +0100 Subject: [PATCH 14/17] Remove test struct with nested field ref - Can't use nested field refs with .NET 4.8. ***NO_CI*** --- Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs index 1824ab0f..d02d1c00 100644 --- a/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs +++ b/Tests/NFUnitTestSystemLib/RuntimeHelpersTests.cs @@ -23,7 +23,7 @@ public static void IsReferenceOrContainsReferences() Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>()); Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences>()); //Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); - Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); + //Assert.IsTrue(RuntimeHelpers.IsReferenceOrContainsReferences()); } private struct StructWithoutReferences @@ -60,9 +60,9 @@ private ref struct RefStructWithReferences // } //} - private ref struct RefStructWithNestedRef - { - public Span a; - } + //private ref struct RefStructWithNestedRef + //{ + // public Span a; + //} } } From d2b908e27f9dbf2323c636431791ac2ffea28530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 25 Sep 2025 23:09:30 +0100 Subject: [PATCH 15/17] Work CI-CD - Fix task to download nanclr artifact for unit tests. - GitHub auth now uses token directly. - Fix condition for pre-release on develop branch. ***NO_CI*** --- .../check-nf-interpreter-to-test.yml | 16 +++++++++++----- azure-pipelines.yml | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/azure-pipelines-templates/check-nf-interpreter-to-test.yml b/azure-pipelines-templates/check-nf-interpreter-to-test.yml index 9fdf8513..d00acd5a 100644 --- a/azure-pipelines-templates/check-nf-interpreter-to-test.yml +++ b/azure-pipelines-templates/check-nf-interpreter-to-test.yml @@ -10,13 +10,19 @@ steps: targetType: "inline" script: | - # compute authorization header in format "AUTHORIZATION: basic 'encoded token'" - # 'encoded token' is the Base64 of the string "nfbot:personal-token" - $auth = "basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("nfbot:$(GitHubToken)")))" + # prepare GitHub API headers using token auth + $headers = @{ + Authorization = "token $env:GITHUB_TOKEN" + 'User-Agent' = 'azure-pipelines' + Accept = 'application/vnd.github+json' + } # find PR "Getting PR #$env:System_PullRequest_PullRequestNumber details..." | Write-Host -ForegroundColor White -NoNewline - $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$env:Build_Repository_Name/pulls/$env:System_PullRequest_PullRequestNumber" -Header @{"Authorization"="$auth"} -ContentType "application/json" -Method GET + $pr = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/$env:Build_Repository_Name/pulls/$env:System_PullRequest_PullRequestNumber" ` + -Headers $headers ` + -Method GET if($($pr.number) -eq "$env:System_PullRequest_PullRequestNumber") { @@ -56,7 +62,7 @@ steps: definition: '34' buildVersionToDownload: specific allowFailedBuilds: true - pipelineId: $(NFINTERPRETER_BUILDID) + buildId: $(NFINTERPRETER_BUILDID) artifactName: 'nanoclr_cli' targetPath: '$(Pipeline.Workspace)/nanoclr' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 440fdca7..e24c2aac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -148,7 +148,7 @@ jobs: releaseNotesInline: 'Check the [changelog]($(Build.Repository.Uri)/blob/$(Build.SourceBranchName)/CHANGELOG.md).

Install from NuGet


The following NuGet packages are available for download from this release:
:package: [.NET](https://www.nuget.org/packages/$(nugetPackageName)/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION).
:package: [.NET (without Reflection)](https://www.nuget.org/packages/$(nugetPackageName).NoReflection/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION)' assets: '$(Build.ArtifactStagingDirectory)/*.nupkg' assetUploadMode: replace - isPreRelease: false + isPreRelease: $[eq(variables['Build.SourceBranchName'], 'develop')] addChangeLog: false ############################## From 668efa7d1d5d31c8e2946ebb2d81c23d63ad10a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Sat, 27 Sep 2025 12:50:11 +0100 Subject: [PATCH 16/17] Bump test framework sub-module @602d512 --- nanoFramework.TestFramework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nanoFramework.TestFramework b/nanoFramework.TestFramework index 18e4b3d4..602d512e 160000 --- a/nanoFramework.TestFramework +++ b/nanoFramework.TestFramework @@ -1 +1 @@ -Subproject commit 18e4b3d4b0c6b647bc3c2a8a0d11a53c059145a7 +Subproject commit 602d512e74b0508a68f21f2ec9a9bc5d29491c48 From 5b23ee99c06cf3c748b16b64cd6f6218e7bde7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Thu, 6 Nov 2025 04:51:02 +0000 Subject: [PATCH 17/17] Add `SZArrayHelper` and `Unsafe.As` (#257) --- nanoFramework.CoreLibrary/CoreLibrary.nfproj | 3 +- nanoFramework.CoreLibrary/System/Array.cs | 152 ++++++++++++++++++ .../System/Runtime/CompilerServices/Unsafe.cs | 23 +++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 nanoFramework.CoreLibrary/System/Runtime/CompilerServices/Unsafe.cs diff --git a/nanoFramework.CoreLibrary/CoreLibrary.nfproj b/nanoFramework.CoreLibrary/CoreLibrary.nfproj index e1501725..0bd250ab 100644 --- a/nanoFramework.CoreLibrary/CoreLibrary.nfproj +++ b/nanoFramework.CoreLibrary/CoreLibrary.nfproj @@ -74,6 +74,7 @@ + @@ -262,4 +263,4 @@ - + \ No newline at end of file diff --git a/nanoFramework.CoreLibrary/System/Array.cs b/nanoFramework.CoreLibrary/System/Array.cs index 86d93f12..c9cf7254 100644 --- a/nanoFramework.CoreLibrary/System/Array.cs +++ b/nanoFramework.CoreLibrary/System/Array.cs @@ -3,6 +3,10 @@ using System.Collections; using System.Runtime.CompilerServices; +#if NANOCLR_REFLECTION +using System.Collections.Generic; +using System.Diagnostics; +#endif // NANOCLR_REFLECTION namespace System { @@ -429,5 +433,153 @@ public void Reset() _index = _startIndex - 1; } } + +#if NANOCLR_REFLECTION +#pragma warning disable CA1822 // Mark members as static + //---------------------------------------------------------------------------------------- + // ! READ THIS BEFORE YOU WORK ON THIS CLASS. + // + // The methods on this class must be written VERY carefully to avoid introducing security holes. + // That's because they are invoked with special "this"! The "this" object + // for all of these methods are not SZArrayHelper objects. Rather, they are of type U[] + // where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will + // see a lot of expressions that cast "this" "T[]". + // + // This class is needed to allow an SZ array of type T[] to expose IList, + // IList, etc., etc. all the way up to IList. When the following call is + // made: + // + // ((IList) (new U[n])).SomeIListMethod() + // + // the interface stub dispatcher treats this as a special case, loads up SZArrayHelper, + // finds the corresponding generic method (matched simply by method name), instantiates + // it for type and executes it. + // + // The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be + // array that is castable to "T[]" (i.e. for primitives and valuetypes, it will be exactly + // "T[]" - for orefs, it may be a "U[]" where U derives from T.) + //---------------------------------------------------------------------------------------- + internal sealed class SZArrayHelper + { + // It is never legal to instantiate this class. + private SZArrayHelper() + { + Debug.Assert(false, "Hey! How'd I get here?"); + } + + internal IEnumerator GetEnumerator() + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + T[] @this = Unsafe.As(this); + int length = @this.Length; + return length == 0 ? SZGenericArrayEnumerator.Empty : new SZGenericArrayEnumerator(@this, length); + } + + private void CopyTo(T[] array, int index) + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + + T[] @this = Unsafe.As(this); + Array.Copy(@this, 0, array, index, @this.Length); + } + + internal int get_Count() + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + T[] @this = Unsafe.As(this); + return @this.Length; + } + + internal T get_Item(int index) + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + + T[] @this = Unsafe.As(this); + if ((uint)index >= (uint)@this.Length) + { +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one + throw new ArgumentOutOfRangeException(); +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one + } + + return @this[index]; + } + + internal void set_Item(int index, T value) + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + T[] @this = Unsafe.As(this); + if ((uint)index >= (uint)@this.Length) + { +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one + throw new ArgumentOutOfRangeException(); +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one + } + + @this[index] = value; + } + + private void Add(T _) + { + // Not meaningful for arrays. + throw new NotSupportedException(); + } + + private bool Contains(T value) + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + T[] @this = Unsafe.As(this); + return Array.IndexOf(@this, value, 0, @this.Length) >= 0; + } + + private bool get_IsReadOnly() + { + return true; + } + + private void Clear() + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + + throw new NotSupportedException(); + } + + private int IndexOf(T value) + { + // ! Warning: "this" is an array, not an SZArrayHelper. See comments above + // ! or you may introduce a security hole! + T[] @this = Unsafe.As(this); + return Array.IndexOf(@this, value, 0, @this.Length); + } + + private void Insert(int _, T _1) + { + // Not meaningful for arrays + throw new NotSupportedException(); + } + + private bool Remove(T _) + { + // Not meaningful for arrays + throw new NotSupportedException(); + return default; + } + + private void RemoveAt(int _) + { + // Not meaningful for arrays + throw new NotSupportedException(); + } + } +#pragma warning restore CA1822 + +#endif } } diff --git a/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/Unsafe.cs b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/Unsafe.cs new file mode 100644 index 00000000..6d784b10 --- /dev/null +++ b/nanoFramework.CoreLibrary/System/Runtime/CompilerServices/Unsafe.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NANOCLR_REFLECTION +#nullable enable + +namespace System.Runtime.CompilerServices +{ + /// + /// Contains generic, low-level functionality for manipulating pointers. + /// + public static unsafe partial class Unsafe + { + /// + /// Casts the given object to the specified type, performs no dynamic type checking. + /// + /// The target reference type. The return value will be of this type. + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern T? As(object? o) where T : class?; + } +} + +#endif