diff --git a/QuantConnectStubsGenerator.Tests/RendererTests.cs b/QuantConnectStubsGenerator.Tests/RendererTests.cs index 97c65a0..57ed498 100644 --- a/QuantConnectStubsGenerator.Tests/RendererTests.cs +++ b/QuantConnectStubsGenerator.Tests/RendererTests.cs @@ -366,6 +366,145 @@ def __isub__(self, item: QuantConnect_Test__EventContainer_Callable) -> typing.S ... " }).SetName("GeneratesEventContainerClassForEventsDelegates"), + + // GeneratesContainerMethodsForDictionaries + new TestCaseData( + new Dictionary() + { + { + "IDictionary.cs", + @" +namespace System.Collections +{ + public interface IDictionary : ICollection + { + } + + public interface IDictionary : ICollection + { + } +} +" + }, + { + "KeyValuePair.cs", + @" +namespace System.Collections.Generic +{ + public readonly struct KeyValuePair + { + } +} +" + }, + { + "Test.cs", + @" +using System; +using System.Collections; +using System.Collections.Generic; + +namespace QuantConnect.Test +{ + public class TestDictionary : IDictionary + { + private readonly Dictionary _data = new(); + + public int Count => _data.Count + + public bool ContainsKey(TKey key) + { + return data.ContainsKey(key); + } + } + + public class TestDictionary2 : IEnumerable> + { + private readonly List> _data = new(); + + public int Count => _data.Count + + public bool ContainsKey(TKey key) + { + return data.ContainsKey(key); + } + } +}" + } + }, + new[] + { + @" +from typing import overload +from enum import Enum +import abc + +import System.Collections + +class IDictionary(System.Collections.ICollection, metaclass=abc.ABCMeta): + """"""This class has no documentation."""""" +", + @" +from typing import overload +from enum import Enum +import typing + +import System.Collections.Generic + +System_Collections_Generic_KeyValuePair_TKey = typing.TypeVar(""System_Collections_Generic_KeyValuePair_TKey"") +System_Collections_Generic_KeyValuePair_TValue = typing.TypeVar(""System_Collections_Generic_KeyValuePair_TValue"") + +class KeyValuePair(typing.Generic[System_Collections_Generic_KeyValuePair_TKey, System_Collections_Generic_KeyValuePair_TValue]): + """"""This class has no documentation."""""" +", + @" +from typing import overload +from enum import Enum +import typing + +import QuantConnect.Test +import System +import System.Collections +import System.Collections.Generic + +QuantConnect_Test_TestDictionary_TKey = typing.TypeVar(""QuantConnect_Test_TestDictionary_TKey"") +QuantConnect_Test_TestDictionary_TValue = typing.TypeVar(""QuantConnect_Test_TestDictionary_TValue"") +QuantConnect_Test_TestDictionary2_TKey = typing.TypeVar(""QuantConnect_Test_TestDictionary2_TKey"") +QuantConnect_Test_TestDictionary2_TValue = typing.TypeVar(""QuantConnect_Test_TestDictionary2_TValue"") + +class TestDictionary(typing.Generic[QuantConnect_Test_TestDictionary_TKey, QuantConnect_Test_TestDictionary_TValue], System.Object, System.Collections.IDictionary): + """"""This class has no documentation."""""" + + @property + def count(self) -> int: + ... + + def __contains__(self, key: QuantConnect_Test_TestDictionary_TKey) -> bool: + ... + + def __len__(self) -> int: + ... + + def contains_key(self, key: QuantConnect_Test_TestDictionary_TKey) -> bool: + ... + +class TestDictionary2(typing.Generic[QuantConnect_Test_TestDictionary2_TKey, QuantConnect_Test_TestDictionary2_TValue], System.Object, typing.Iterable[System.Collections.Generic.KeyValuePair[QuantConnect_Test_TestDictionary2_TKey, QuantConnect_Test_TestDictionary2_TValue]]): + """"""This class has no documentation."""""" + + @property + def count(self) -> int: + ... + + def __contains__(self, key: QuantConnect_Test_TestDictionary2_TKey) -> bool: + ... + + def __len__(self) -> int: + ... + + def contains_key(self, key: QuantConnect_Test_TestDictionary2_TKey) -> bool: + ... +" + }).SetName("GeneratesContainerMethodsForDictionaries"), }; private class TestGenerator : Generator diff --git a/QuantConnectStubsGenerator/Parser/MethodParser.cs b/QuantConnectStubsGenerator/Parser/MethodParser.cs index a207d18..756f6a0 100644 --- a/QuantConnectStubsGenerator/Parser/MethodParser.cs +++ b/QuantConnectStubsGenerator/Parser/MethodParser.cs @@ -280,6 +280,7 @@ private void VisitMethod( _currentClass.Methods.Add(method); ImprovePythonAccessorIfNecessary(method); + ImproveDictionaryDefinitionIfNecessary(node, method); } private Parameter ParseParameter(ParameterSyntax syntax, bool avoidImplicitConversionTypes) @@ -405,6 +406,11 @@ private void AddContainerMethodsIfNecessary(MethodDeclarationSyntax node) return; } + AddContainerMethods(node); + } + + private void AddContainerMethods(MethodDeclarationSyntax node) + { VisitMethod(node, "__contains__", node.ParameterList.Parameters, _typeConverter.GetType(node.ReturnType, skipTypeNormalization: SkipTypeNormalization), null, false); if (_currentClass.Methods.All(m => m.Name != "__len__")) @@ -450,6 +456,19 @@ private void ImprovePythonAccessorIfNecessary(Method newMethod) _currentClass.Methods.Remove(existingMethod); } + private void ImproveDictionaryDefinitionIfNecessary(MemberDeclarationSyntax node, Method method) + { + // We add container methods to classes that implement Count and ContainsKey + // because Python.Net does this to add support for operators like 'in' to work with these types + if (node is MethodDeclarationSyntax methodNode && + method.Name == "ContainsKey" && + method.Parameters.Count == 1 && + _currentClass.Properties.Any(property => property.Name == "Count")) + { + AddContainerMethods(methodNode); + } + } + /// /// Returns whether the provided documentation string suggests that a certain type is a pandas DataFrame. ///