Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snake-case static readonly fields as constants #84

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion src/embed_tests/ClassManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,20 @@ public class SnakeCaseNamesTesClass
public static string SettablePublicStaticStringField = "settable_public_static_string_field";

public string PublicStringProperty { get; set; } = "public_string_property";
public string PublicStringGetOnlyProperty { get; } = "public_string_get_only_property";
public static string PublicStaticStringProperty { get; set; } = "public_static_string_property";
public static string PublicStaticReadonlyStringGetterOnlyProperty { get; } = "public_static_readonly_string_getter_only_property";
public static string PublicStaticReadonlyStringPrivateSetterProperty { get; private set; } = "public_static_readonly_string_private_setter_property";
public static string PublicStaticReadonlyStringProtectedSetterProperty { get; protected set; } = "public_static_readonly_string_protected_setter_property";
public static string PublicStaticReadonlyStringInternalSetterProperty { get; internal set; } = "public_static_readonly_string_internal_setter_property";
public static string PublicStaticReadonlyStringProtectedInternalSetterProperty { get; protected internal set; } = "public_static_readonly_string_protected_internal_setter_property";
public static string PublicStaticReadonlyStringExpressionBodiedProperty => "public_static_readonly_string_expression_bodied_property";

protected string ProtectedStringGetOnlyProperty { get; } = "protected_string_get_only_property";
protected static string ProtectedStaticStringProperty { get; set; } = "protected_static_string_property";
protected static string ProtectedStaticReadonlyStringGetterOnlyProperty { get; } = "protected_static_readonly_string_getter_only_property";
protected static string ProtectedStaticReadonlyStringPrivateSetterProperty { get; private set; } = "protected_static_readonly_string_private_setter_property";
protected static string ProtectedStaticReadonlyStringExpressionBodiedProperty => "protected_static_readonly_string_expression_bodied_property";

public event EventHandler<string> PublicStringEvent;
public static event EventHandler<string> PublicStaticStringEvent;
Expand Down Expand Up @@ -111,9 +124,9 @@ public void BindsSnakeCaseClassMethods(string originalMethodName, string snakeCa
[TestCase("PublicStringField", "public_string_field")]
[TestCase("PublicStaticStringField", "public_static_string_field")]
[TestCase("PublicReadonlyStringField", "public_readonly_string_field")]
[TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")]
// Constants
[TestCase("PublicConstStringField", "PUBLIC_CONST_STRING_FIELD")]
[TestCase("PublicStaticReadonlyStringField", "PUBLIC_STATIC_READONLY_STRING_FIELD")]
public void BindsSnakeCaseClassFields(string originalFieldName, string snakeCaseFieldName)
{
using var obj = new SnakeCaseNamesTesClass().ToPython();
Expand Down Expand Up @@ -187,14 +200,40 @@ def SetSnakeCaseStaticProperty(value):
}

[TestCase("PublicStringProperty", "public_string_property")]
[TestCase("PublicStringGetOnlyProperty", "public_string_get_only_property")]
[TestCase("PublicStaticStringProperty", "public_static_string_property")]
[TestCase("PublicStaticReadonlyStringPrivateSetterProperty", "public_static_readonly_string_private_setter_property")]
[TestCase("PublicStaticReadonlyStringProtectedSetterProperty", "public_static_readonly_string_protected_setter_property")]
[TestCase("PublicStaticReadonlyStringInternalSetterProperty", "public_static_readonly_string_internal_setter_property")]
[TestCase("PublicStaticReadonlyStringProtectedInternalSetterProperty", "public_static_readonly_string_protected_internal_setter_property")]
[TestCase("ProtectedStringGetOnlyProperty", "protected_string_get_only_property")]
[TestCase("ProtectedStaticStringProperty", "protected_static_string_property")]
[TestCase("ProtectedStaticReadonlyStringPrivateSetterProperty", "protected_static_readonly_string_private_setter_property")]
// Constants
[TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "PUBLIC_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
[TestCase("PublicStaticReadonlyStringExpressionBodiedProperty", "PUBLIC_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]
[TestCase("ProtectedStaticReadonlyStringGetterOnlyProperty", "PROTECTED_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
[TestCase("ProtectedStaticReadonlyStringExpressionBodiedProperty", "PROTECTED_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]

public void BindsSnakeCaseClassProperties(string originalPropertyName, string snakeCasePropertyName)
{
using var obj = new SnakeCaseNamesTesClass().ToPython();
var expectedValue = originalPropertyName switch
{
"PublicStringProperty" => "public_string_property",
"PublicStringGetOnlyProperty" => "public_string_get_only_property",
"PublicStaticStringProperty" => "public_static_string_property",
"PublicStaticReadonlyStringPrivateSetterProperty" => "public_static_readonly_string_private_setter_property",
"PublicStaticReadonlyStringProtectedSetterProperty" => "public_static_readonly_string_protected_setter_property",
"PublicStaticReadonlyStringInternalSetterProperty" => "public_static_readonly_string_internal_setter_property",
"PublicStaticReadonlyStringProtectedInternalSetterProperty" => "public_static_readonly_string_protected_internal_setter_property",
"PublicStaticReadonlyStringGetterOnlyProperty" => "public_static_readonly_string_getter_only_property",
"PublicStaticReadonlyStringExpressionBodiedProperty" => "public_static_readonly_string_expression_bodied_property",
"ProtectedStringGetOnlyProperty" => "protected_string_get_only_property",
"ProtectedStaticStringProperty" => "protected_static_string_property",
"ProtectedStaticReadonlyStringGetterOnlyProperty" => "protected_static_readonly_string_getter_only_property",
"ProtectedStaticReadonlyStringPrivateSetterProperty" => "protected_static_readonly_string_private_setter_property",
"ProtectedStaticReadonlyStringExpressionBodiedProperty" => "protected_static_readonly_string_expression_bodied_property",
_ => throw new ArgumentException("Invalid property name")
};

Expand Down
90 changes: 90 additions & 0 deletions src/embed_tests/TestUtil.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Reflection;

using NUnit.Framework;

using Python.Runtime;
Expand All @@ -7,6 +9,8 @@ namespace Python.EmbeddingTest
[TestFixture]
public class TestUtil
{
private static BindingFlags _bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

[TestCase("TestCamelCaseString", "test_camel_case_string")]
[TestCase("testCamelCaseString", "test_camel_case_string")]
[TestCase("TestCamelCaseString123 ", "test_camel_case_string123")]
Expand All @@ -19,5 +23,91 @@ public void ConvertsNameToSnakeCase(string name, string expected)
{
Assert.AreEqual(expected, name.ToSnakeCase());
}

[TestCase("TestNonConstField1", "test_non_const_field1")]
[TestCase("TestNonConstField2", "test_non_const_field2")]
[TestCase("TestNonConstField3", "test_non_const_field3")]
[TestCase("TestNonConstField4", "test_non_const_field4")]
public void ConvertsNonConstantFieldsToSnakeCase(string fieldName, string expected)
{
var fi = typeof(TestClass).GetField(fieldName, _bindingFlags);
Assert.AreEqual(expected, fi.ToSnakeCase());
}

[TestCase("TestConstField1", "TEST_CONST_FIELD1")]
[TestCase("TestConstField2", "TEST_CONST_FIELD2")]
[TestCase("TestConstField3", "TEST_CONST_FIELD3")]
[TestCase("TestConstField4", "TEST_CONST_FIELD4")]
public void ConvertsConstantFieldsToFullCapitalCase(string fieldName, string expected)
{
var fi = typeof(TestClass).GetField(fieldName, _bindingFlags);
Assert.AreEqual(expected, fi.ToSnakeCase());
}

[TestCase("TestNonConstProperty1", "test_non_const_property1")]
[TestCase("TestNonConstProperty2", "test_non_const_property2")]
[TestCase("TestNonConstProperty3", "test_non_const_property3")]
[TestCase("TestNonConstProperty4", "test_non_const_property4")]
[TestCase("TestNonConstProperty5", "test_non_const_property5")]
[TestCase("TestNonConstProperty6", "test_non_const_property6")]
[TestCase("TestNonConstProperty7", "test_non_const_property7")]
[TestCase("TestNonConstProperty8", "test_non_const_property8")]
[TestCase("TestNonConstProperty9", "test_non_const_property9")]
[TestCase("TestNonConstProperty10", "test_non_const_property10")]
[TestCase("TestNonConstProperty11", "test_non_const_property11")]
[TestCase("TestNonConstProperty12", "test_non_const_property12")]
[TestCase("TestNonConstProperty13", "test_non_const_property13")]
[TestCase("TestNonConstProperty14", "test_non_const_property14")]
[TestCase("TestNonConstProperty15", "test_non_const_property15")]
[TestCase("TestNonConstProperty16", "test_non_const_property16")]
public void ConvertsNonConstantPropertiesToSnakeCase(string propertyName, string expected)
{
var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags);
Assert.AreEqual(expected, pi.ToSnakeCase());
}

[TestCase("TestConstProperty1", "TEST_CONST_PROPERTY1")]
[TestCase("TestConstProperty2", "TEST_CONST_PROPERTY2")]
[TestCase("TestConstProperty3", "TEST_CONST_PROPERTY3")]
public void ConvertsConstantPropertiesToFullCapitalCase(string propertyName, string expected)
{
var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags);
Assert.AreEqual(expected, pi.ToSnakeCase());
}

private class TestClass
{
public string TestNonConstField1 = "TestNonConstField1";
protected string TestNonConstField2 = "TestNonConstField2";
public static string TestNonConstField3 = "TestNonConstField3";
protected static string TestNonConstField4 = "TestNonConstField4";

public const string TestConstField1 = "TestConstField1";
protected const string TestConstField2 = "TestConstField2";
public static readonly string TestConstField3 = "TestConstField3";
protected static readonly string TestConstField4 = "TestConstField4";

public string TestNonConstProperty1 { get; set; } = "TestNonConstProperty1";
protected string TestNonConstProperty2 { get; set; } = "TestNonConstProperty2";
public string TestNonConstProperty3 { get; } = "TestNonConstProperty3";
protected string TestNonConstProperty4 { get; } = "TestNonConstProperty4";
public string TestNonConstProperty5 { get; private set; } = "TestNonConstProperty5";
protected string TestNonConstProperty6 { get; private set; } = "TestNonConstProperty6";
public string TestNonConstProperty7 { get; protected set; } = "TestNonConstProperty7";
public string TestNonConstProperty8 { get; internal set; } = "TestNonConstProperty8";
public string TestNonConstProperty9 { get; protected internal set; } = "TestNonConstProperty9";
public static string TestNonConstProperty10 { get; set; } = "TestNonConstProperty10";
protected static string TestNonConstProperty11 { get; set; } = "TestNonConstProperty11";
public static string TestNonConstProperty12 { get; private set; } = "TestNonConstProperty12";
protected static string TestNonConstProperty13 { get; private set; } = "TestNonConstProperty13";
public static string TestNonConstProperty14 { get; protected set; } = "TestNonConstProperty14";
public static string TestNonConstProperty15 { get; internal set; } = "TestNonConstProperty15";
public static string TestNonConstProperty16 { get; protected internal set; } = "TestNonConstProperty16";


public static string TestConstProperty1 => "TestConstProperty1";
public static string TestConstProperty2 { get; } = "TestConstProperty2";
protected static string TestConstProperty3 { get; } = "TestConstProperty3";
}
}
}
4 changes: 2 additions & 2 deletions src/perf_tests/Python.PerformanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="quantconnect.pythonnet" Version="2.0.30" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.31" GeneratePathProperty="true">
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup>
Expand All @@ -25,7 +25,7 @@
</Target>

<Target Name="CopyBaseline" AfterTargets="Build">
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.30\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.31\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
11 changes: 2 additions & 9 deletions src/runtime/ClassManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@
}

ob = new PropertyObject(pi);
AddMember(pi.Name, pi.Name.ToSnakeCase(), ob.AllocObject());
AddMember(pi.Name, pi.ToSnakeCase(), ob.AllocObject());
continue;

case MemberTypes.Field:
Expand All @@ -543,14 +543,7 @@
continue;
}
ob = new FieldObject(fi);

var pepName = fi.Name.ToSnakeCase();
if (fi.IsLiteral)
{
pepName = pepName.ToUpper();
}

AddMember(fi.Name, pepName, ob.AllocObject());
AddMember(fi.Name, fi.ToSnakeCase(), ob.AllocObject());
continue;

case MemberTypes.Event:
Expand Down Expand Up @@ -630,7 +623,7 @@
/// </summary>
private class ClassInfo
{
public Indexer? indexer;

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.8, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.7, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.10, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macos, 3.11, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macos, 3.9, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macos, 3.7, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 626 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (windows, 3.8, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public readonly Dictionary<string, PyObject> members = new();

internal ClassInfo()
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]

[assembly: AssemblyVersion("2.0.30")]
[assembly: AssemblyFileVersion("2.0.30")]
[assembly: AssemblyVersion("2.0.31")]
[assembly: AssemblyFileVersion("2.0.31")]
2 changes: 1 addition & 1 deletion src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<RootNamespace>Python.Runtime</RootNamespace>
<AssemblyName>Python.Runtime</AssemblyName>
<PackageId>QuantConnect.pythonnet</PackageId>
<Version>2.0.30</Version>
<Version>2.0.31</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>
Expand Down
37 changes: 32 additions & 5 deletions src/runtime/Util/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -41,7 +42,7 @@ internal static long ReadInt64(BorrowedReference ob, int offset)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe static T* ReadPtr<T>(BorrowedReference ob, int offset)
where T: unmanaged
where T : unmanaged
{
Debug.Assert(offset >= 0);
IntPtr ptr = Marshal.ReadIntPtr(ob.DangerousGetAddress(), offset);
Expand Down Expand Up @@ -152,7 +153,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;

public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
where T: class
where T : class
{
foreach (var item in source)
{
Expand All @@ -166,7 +167,7 @@ public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
/// <remarks>
/// Reference: https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
/// </remarks>
public static string ToSnakeCase(this string name)
public static string ToSnakeCase(this string name, bool constant = false)
{
var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
var previousCategory = default(UnicodeCategory?);
Expand Down Expand Up @@ -196,8 +197,10 @@ public static string ToSnakeCase(this string name)
{
builder.Append('_');
}

currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture);
if (!constant)
{
currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture);
}
break;

case UnicodeCategory.LowercaseLetter:
Expand All @@ -206,6 +209,10 @@ public static string ToSnakeCase(this string name)
{
builder.Append('_');
}
if (constant)
{
currentChar = char.ToUpper(currentChar, CultureInfo.InvariantCulture);
}
break;

default:
Expand All @@ -222,5 +229,25 @@ public static string ToSnakeCase(this string name)

return builder.ToString();
}

/// <summary>
/// Converts the specified field name to snake case.
/// const and static readonly fields are considered as constants and are converted to uppercase.
/// </summary>
public static string ToSnakeCase(this FieldInfo fieldInfo)
{
return fieldInfo.Name.ToSnakeCase(fieldInfo.IsLiteral || (fieldInfo.IsStatic && fieldInfo.IsInitOnly));
}

/// <summary>
/// Converts the specified property name to snake case.
/// Static properties without a setter are considered as constants and are converted to uppercase.
/// </summary>
public static string ToSnakeCase(this PropertyInfo propertyInfo)
{
var constant = propertyInfo.CanRead && !propertyInfo.CanWrite &&
(propertyInfo.GetGetMethod()?.IsStatic ?? propertyInfo.GetGetMethod(nonPublic: true)?.IsStatic ?? false);
return propertyInfo.Name.ToSnakeCase(constant);
}
}
}
Loading