From 9faeb3d64544e0adb131d546fe7023d68bea6c6d Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 19 Jul 2024 12:47:41 +1000 Subject: [PATCH 1/3] Create a test for old-style annotations for List, Tuple and Dict --- Integration.Tests/BasicTest.cs | 11 ++++++++--- Integration.Tests/Integration.Tests.csproj | 2 ++ Integration.Tests/python/test_typing.py | 10 ++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 Integration.Tests/python/test_typing.py diff --git a/Integration.Tests/BasicTest.cs b/Integration.Tests/BasicTest.cs index e7526292..882a0f7e 100644 --- a/Integration.Tests/BasicTest.cs +++ b/Integration.Tests/BasicTest.cs @@ -1,6 +1,5 @@ using Python.Generated; using Python.Runtime; -using System.Collections.Immutable; using System.Reflection; namespace Integration.Tests; @@ -22,8 +21,6 @@ public void TestBasic() Assert.Equal([1, 2, 3], testModule.TestListOfInts([1, 2, 3])); Assert.Equal("hello world", testModule.TestTwoStrings("hello ", "world")); Assert.Equal(["hello", "world", "this", "is", "a", "test"], testModule.TestTwoListsOfStrings(["hello", "world"], ["this", "is", "a", "test"])); - - } [Fact] @@ -70,4 +67,12 @@ public void TestDicts() var result3 = testDicts.TestDictStrDictInt(testDictDict); Assert.Equal(1, result3["hello"]["world"]); } + + [Fact] + public void TestOldTypeBuiltinTypes() + { + var testTyping = testEnv.Env.TestTyping(); + + Assert.Equal([1, 2, 3], testTyping.TestOldListStyle([1, 2, 3])); + } } \ No newline at end of file diff --git a/Integration.Tests/Integration.Tests.csproj b/Integration.Tests/Integration.Tests.csproj index 09b46317..1f4d63f9 100644 --- a/Integration.Tests/Integration.Tests.csproj +++ b/Integration.Tests/Integration.Tests.csproj @@ -14,6 +14,7 @@ + @@ -29,6 +30,7 @@ Always + diff --git a/Integration.Tests/python/test_typing.py b/Integration.Tests/python/test_typing.py new file mode 100644 index 00000000..424b0c6a --- /dev/null +++ b/Integration.Tests/python/test_typing.py @@ -0,0 +1,10 @@ +from typing import List, Tuple, Dict + +def test_old_list_style(a: List[int]) -> List[int]: + return a + +def test_old_tuple_style(a: Tuple[int, int]) -> Tuple[int, int]: + return a + +def test_old_dict_style(a: Dict[str, int]) -> Dict[str, int]: + return a From e55cb403d7511f60165499965bd839d571a5e185 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 19 Jul 2024 12:55:27 +1000 Subject: [PATCH 2/3] Lowercase the generics to support the old typing.X generics --- Integration.Tests/BasicTest.cs | 5 ++++ .../TypeReflectionTests.cs | 23 +++++++++++++++++++ .../Reflection/TypeReflection.cs | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Integration.Tests/BasicTest.cs b/Integration.Tests/BasicTest.cs index 882a0f7e..8d10a9d9 100644 --- a/Integration.Tests/BasicTest.cs +++ b/Integration.Tests/BasicTest.cs @@ -74,5 +74,10 @@ public void TestOldTypeBuiltinTypes() var testTyping = testEnv.Env.TestTyping(); Assert.Equal([1, 2, 3], testTyping.TestOldListStyle([1, 2, 3])); + + IReadOnlyDictionary testDict = new Dictionary { { "hello", 1 }, { "world", 2 } }; + var result = testTyping.TestOldDictStyle(testDict); + Assert.Equal(1, result["hello"]); + Assert.Equal(2, result["world"]); } } \ No newline at end of file diff --git a/PythonSourceGenerator.Tests/TypeReflectionTests.cs b/PythonSourceGenerator.Tests/TypeReflectionTests.cs index 377cd356..67bca79e 100644 --- a/PythonSourceGenerator.Tests/TypeReflectionTests.cs +++ b/PythonSourceGenerator.Tests/TypeReflectionTests.cs @@ -32,4 +32,27 @@ public void AsPredefinedType(string pythonType, string expectedType) var reflectedType = TypeReflection.AsPredefinedType(result.Value); Assert.Equal(expectedType, reflectedType.ToString()); } + + [Theory] + [InlineData("List[int]", "IEnumerable")] + [InlineData("List[str]", "IEnumerable")] + [InlineData("List[float]", "IEnumerable")] + [InlineData("List[bool]", "IEnumerable")] + [InlineData("List[object]", "IEnumerable")] + [InlineData("Tuple[int, int]", "Tuple")] + [InlineData("Tuple[str, str]", "Tuple")] + [InlineData("Tuple[float, float]", "Tuple")] + [InlineData("Tuple[bool, bool]", "Tuple")] + [InlineData("Tuple[str, Any]", "Tuple")] + [InlineData("Tuple[str, list[int]]", "Tuple>")] + [InlineData("Dict[str, int]", "IReadOnlyDictionary")] + [InlineData("Tuple[int, int, Tuple[int, int]]", "Tuple>")] + public void AsPredefinedTypeOldTypeNames(string pythonType, string expectedType) + { + var tokens = PythonSignatureTokenizer.Instance.Tokenize(pythonType); + var result = PythonSignatureParser.PythonTypeDefinitionTokenizer.TryParse(tokens); + Assert.True(result.HasValue); + var reflectedType = TypeReflection.AsPredefinedType(result.Value); + Assert.Equal(expectedType, reflectedType.ToString()); + } } diff --git a/PythonSourceGenerator/Reflection/TypeReflection.cs b/PythonSourceGenerator/Reflection/TypeReflection.cs index 95275ff3..511b91c0 100644 --- a/PythonSourceGenerator/Reflection/TypeReflection.cs +++ b/PythonSourceGenerator/Reflection/TypeReflection.cs @@ -15,7 +15,7 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType) { var genericName = pythonType.Name; // Get last occurrence of ] in pythonType - return genericName switch + return genericName.ToLower() switch { "list" => CreateListType(pythonType.Arguments[0]), "tuple" => CreateTupleType(pythonType.Arguments), From c37f8e51bcb5c44ae5b21227ab0434452c0975e3 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 19 Jul 2024 13:36:33 +1000 Subject: [PATCH 3/3] Support full old type names --- Integration.Tests/BasicTest.cs | 13 ---- Integration.Tests/Integration.Tests.csproj | 2 - Integration.Tests/python/test_typing.py | 10 --- .../BasicSmokeTests.cs | 1 + .../IntegrationTests.cs | 62 ------------------- .../Reflection/TypeReflection.cs | 4 +- 6 files changed, 3 insertions(+), 89 deletions(-) delete mode 100644 Integration.Tests/python/test_typing.py delete mode 100644 PythonSourceGenerator.Tests/IntegrationTests.cs diff --git a/Integration.Tests/BasicTest.cs b/Integration.Tests/BasicTest.cs index 8d10a9d9..b73c8db2 100644 --- a/Integration.Tests/BasicTest.cs +++ b/Integration.Tests/BasicTest.cs @@ -67,17 +67,4 @@ public void TestDicts() var result3 = testDicts.TestDictStrDictInt(testDictDict); Assert.Equal(1, result3["hello"]["world"]); } - - [Fact] - public void TestOldTypeBuiltinTypes() - { - var testTyping = testEnv.Env.TestTyping(); - - Assert.Equal([1, 2, 3], testTyping.TestOldListStyle([1, 2, 3])); - - IReadOnlyDictionary testDict = new Dictionary { { "hello", 1 }, { "world", 2 } }; - var result = testTyping.TestOldDictStyle(testDict); - Assert.Equal(1, result["hello"]); - Assert.Equal(2, result["world"]); - } } \ No newline at end of file diff --git a/Integration.Tests/Integration.Tests.csproj b/Integration.Tests/Integration.Tests.csproj index 1f4d63f9..09b46317 100644 --- a/Integration.Tests/Integration.Tests.csproj +++ b/Integration.Tests/Integration.Tests.csproj @@ -14,7 +14,6 @@ - @@ -30,7 +29,6 @@ Always - diff --git a/Integration.Tests/python/test_typing.py b/Integration.Tests/python/test_typing.py deleted file mode 100644 index 424b0c6a..00000000 --- a/Integration.Tests/python/test_typing.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Tuple, Dict - -def test_old_list_style(a: List[int]) -> List[int]: - return a - -def test_old_tuple_style(a: Tuple[int, int]) -> Tuple[int, int]: - return a - -def test_old_dict_style(a: Dict[str, int]) -> Dict[str, int]: - return a diff --git a/PythonSourceGenerator.Tests/BasicSmokeTests.cs b/PythonSourceGenerator.Tests/BasicSmokeTests.cs index a582fdf5..916e3422 100644 --- a/PythonSourceGenerator.Tests/BasicSmokeTests.cs +++ b/PythonSourceGenerator.Tests/BasicSmokeTests.cs @@ -24,6 +24,7 @@ public BasicSmokeTest(TestEnvironment testEnv) [InlineData("def hello_world(name: str) -> int:\n ...\n", "long HelloWorld(string name)")] [InlineData("def hello_world(name: str, age: int) -> str:\n ...\n", "string HelloWorld(string name, long age)")] [InlineData("def hello_world(numbers: list[float]) -> list[int]:\n ...\n", "IEnumerable HelloWorld(IEnumerable numbers)")] + [InlineData("def hello_world(numbers: List[float]) -> List[int]:\n ...\n", "IEnumerable HelloWorld(IEnumerable numbers)")] [InlineData("def hello_world(a: bool, b: str, c: list[tuple[int, float]]) -> bool: \n ...\n", "bool HelloWorld(bool a, string b, IEnumerable> c)")] [InlineData("def hello_world(a: bool = True, b: str = None) -> bool: \n ...\n", "bool HelloWorld(bool a = true, string b = null)")] [InlineData("def hello_world(a: str, *args) -> None: \n ...\n", "void HelloWorld(string a, Tuple args = null)")] diff --git a/PythonSourceGenerator.Tests/IntegrationTests.cs b/PythonSourceGenerator.Tests/IntegrationTests.cs deleted file mode 100644 index 77f13235..00000000 --- a/PythonSourceGenerator.Tests/IntegrationTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis; -using Python.Runtime; -using PythonSourceGenerator.Parser; -using PythonSourceGenerator.Reflection; - -namespace PythonSourceGenerator.Tests; - -public class IntegrationTests : IClassFixture -{ - TestEnvironment testEnv; - - public IntegrationTests(TestEnvironment testEnv) - { - this.testEnv = testEnv; - } - - private bool Compile(string code, string assemblyName) - { - var tempName = string.Format("{0}_{1:N}", "test", Guid.NewGuid().ToString("N")); - File.WriteAllText(Path.Combine(testEnv.TempDir, $"{tempName}.py"), code); - - // create a Python scope - PythonSignatureParser.TryParseFunctionDefinitions(code, out var functions, out var errors); - Assert.Empty(errors); - var module = ModuleReflection.MethodsFromFunctionDefinitions(functions, "test"); - var csharp = module.Select(m => m.Syntax).Compile(); - - // Check that the sample C# code compiles - string compiledCode = PythonStaticGenerator.FormatClassFromMethods("Python.Generated.Tests", "TestClass", module); - var tree = CSharpSyntaxTree.ParseText(compiledCode); - var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) - .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(IEnumerable<>).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(IReadOnlyDictionary<,>).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(PythonEnvironments.PythonEnvironment).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(typeof(Py).Assembly.Location)) - .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard").Location)) // TODO: (track) Ensure 2.0 - .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "System.Runtime").Location)) - .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "System.Collections").Location)) - .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "System.Linq.Expressions").Location)) - - .AddSyntaxTrees(tree); - var path = testEnv.TempDir + $"/{assemblyName}.dll"; - var result = compilation.Emit(path); - Assert.True(result.Success, compiledCode + "\n" + string.Join("\n", result.Diagnostics)); - // Delete assembly - File.Delete(path); - return result.Success; - } - - [Fact] - public void TestBasicString() - { - var code = """ -def foo(in_: str) -> str: - return in_.upper() -"""; - Assert.True(Compile(code, "stringFoo")); - } -} diff --git a/PythonSourceGenerator/Reflection/TypeReflection.cs b/PythonSourceGenerator/Reflection/TypeReflection.cs index 511b91c0..74b173d0 100644 --- a/PythonSourceGenerator/Reflection/TypeReflection.cs +++ b/PythonSourceGenerator/Reflection/TypeReflection.cs @@ -13,9 +13,9 @@ public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType) // If type is an alias, e.g. "list[int]", "list[float]", etc. if (pythonType.HasArguments()) { - var genericName = pythonType.Name; + var genericName = pythonType.Name.ToLowerInvariant(); // Get last occurrence of ] in pythonType - return genericName.ToLower() switch + return genericName switch { "list" => CreateListType(pythonType.Arguments[0]), "tuple" => CreateTupleType(pythonType.Arguments),