Skip to content

Commit d9e15a7

Browse files
authored
Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException (#1326)
Inlined GenericUtil.GenericsByName into GenericByName. Removed unused GenericUtil.GenericsForType. Other code quality improvements.
1 parent 7149d5e commit d9e15a7

File tree

5 files changed

+48
-60
lines changed

5 files changed

+48
-60
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
- Sean Freitag ([@cowboygneox](https://github.com/cowboygneox))
6161
- Serge Weinstock ([@sweinst](https://github.com/sweinst))
6262
- Simon Mourier ([@smourier](https://github.com/smourier))
63+
- Tom Minka ([@tminka](https://github.com/tminka))
6364
- Viktoria Kovescses ([@vkovec](https://github.com/vkovec))
6465
- Ville M. Vainio ([@vivainio](https://github.com/vivainio))
6566
- Virgil Dupras ([@hsoft](https://github.com/hsoft))

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ details about the cause of the failure
4242
- Made it possible to use `__len__` also on `ICollection<>` interface objects
4343
- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects
4444
- Fixed objects returned by enumerating `PyObject` being disposed too soon
45+
- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException
4546

4647
### Removed
4748

src/runtime/classbase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public virtual IntPtr type_subscript(IntPtr idx)
5454
return c.pyHandle;
5555
}
5656

57-
return Exceptions.RaiseTypeError("no type matches params");
57+
return Exceptions.RaiseTypeError($"{type.Namespace}.{type.Name} does not accept {types.Length} generic parameters");
5858
}
5959

6060
/// <summary>

src/runtime/genericutil.cs

+40-59
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ namespace Python.Runtime
99
/// This class is responsible for efficiently maintaining the bits
1010
/// of information we need to support aliases with 'nice names'.
1111
/// </summary>
12-
internal class GenericUtil
12+
internal static class GenericUtil
1313
{
14+
/// <summary>
15+
/// Maps namespace -> generic base name -> list of generic type names
16+
/// </summary>
1417
private static Dictionary<string, Dictionary<string, List<string>>> mapping;
1518

16-
private GenericUtil()
17-
{
18-
}
19-
2019
public static void Reset()
2120
{
2221
mapping = new Dictionary<string, Dictionary<string, List<string>>>();
@@ -25,29 +24,23 @@ public static void Reset()
2524
/// <summary>
2625
/// Register a generic type that appears in a given namespace.
2726
/// </summary>
27+
/// <param name="t">A generic type definition (<c>t.IsGenericTypeDefinition</c> must be true)</param>
2828
internal static void Register(Type t)
2929
{
3030
if (null == t.Namespace || null == t.Name)
3131
{
3232
return;
3333
}
3434

35-
Dictionary<string, List<string>> nsmap = null;
36-
mapping.TryGetValue(t.Namespace, out nsmap);
37-
if (nsmap == null)
35+
Dictionary<string, List<string>> nsmap;
36+
if (!mapping.TryGetValue(t.Namespace, out nsmap))
3837
{
3938
nsmap = new Dictionary<string, List<string>>();
4039
mapping[t.Namespace] = nsmap;
4140
}
42-
string basename = t.Name;
43-
int tick = basename.IndexOf("`");
44-
if (tick > -1)
45-
{
46-
basename = basename.Substring(0, tick);
47-
}
48-
List<string> gnames = null;
49-
nsmap.TryGetValue(basename, out gnames);
50-
if (gnames == null)
41+
string basename = GetBasename(t.Name);
42+
List<string> gnames;
43+
if (!nsmap.TryGetValue(basename, out gnames))
5144
{
5245
gnames = new List<string>();
5346
nsmap[basename] = gnames;
@@ -60,9 +53,8 @@ internal static void Register(Type t)
6053
/// </summary>
6154
public static List<string> GetGenericBaseNames(string ns)
6255
{
63-
Dictionary<string, List<string>> nsmap = null;
64-
mapping.TryGetValue(ns, out nsmap);
65-
if (nsmap == null)
56+
Dictionary<string, List<string>> nsmap;
57+
if (!mapping.TryGetValue(ns, out nsmap))
6658
{
6759
return null;
6860
}
@@ -75,84 +67,73 @@ public static List<string> GetGenericBaseNames(string ns)
7567
}
7668

7769
/// <summary>
78-
/// xxx
70+
/// Finds a generic type with the given number of generic parameters and the same name and namespace as <paramref name="t"/>.
7971
/// </summary>
8072
public static Type GenericForType(Type t, int paramCount)
8173
{
8274
return GenericByName(t.Namespace, t.Name, paramCount);
8375
}
8476

85-
public static Type GenericByName(string ns, string name, int paramCount)
86-
{
87-
foreach (Type t in GenericsByName(ns, name))
88-
{
89-
if (t.GetGenericArguments().Length == paramCount)
90-
{
91-
return t;
92-
}
93-
}
94-
return null;
95-
}
96-
97-
public static List<Type> GenericsForType(Type t)
98-
{
99-
return GenericsByName(t.Namespace, t.Name);
100-
}
101-
102-
public static List<Type> GenericsByName(string ns, string basename)
77+
/// <summary>
78+
/// Finds a generic type in the given namespace with the given name and number of generic parameters.
79+
/// </summary>
80+
public static Type GenericByName(string ns, string basename, int paramCount)
10381
{
104-
Dictionary<string, List<string>> nsmap = null;
105-
mapping.TryGetValue(ns, out nsmap);
106-
if (nsmap == null)
82+
Dictionary<string, List<string>> nsmap;
83+
if (!mapping.TryGetValue(ns, out nsmap))
10784
{
10885
return null;
10986
}
11087

111-
int tick = basename.IndexOf("`");
112-
if (tick > -1)
113-
{
114-
basename = basename.Substring(0, tick);
115-
}
116-
117-
List<string> names = null;
118-
nsmap.TryGetValue(basename, out names);
119-
if (names == null)
88+
List<string> names;
89+
if (!nsmap.TryGetValue(GetBasename(basename), out names))
12090
{
12191
return null;
12292
}
12393

124-
var result = new List<Type>();
12594
foreach (string name in names)
12695
{
12796
string qname = ns + "." + name;
12897
Type o = AssemblyManager.LookupTypes(qname).FirstOrDefault();
129-
if (o != null)
98+
if (o != null && o.GetGenericArguments().Length == paramCount)
13099
{
131-
result.Add(o);
100+
return o;
132101
}
133102
}
134103

135-
return result;
104+
return null;
136105
}
137106

138107
/// <summary>
139108
/// xxx
140109
/// </summary>
141110
public static string GenericNameForBaseName(string ns, string name)
142111
{
143-
Dictionary<string, List<string>> nsmap = null;
144-
mapping.TryGetValue(ns, out nsmap);
145-
if (nsmap == null)
112+
Dictionary<string, List<string>> nsmap;
113+
if (!mapping.TryGetValue(ns, out nsmap))
146114
{
147115
return null;
148116
}
149-
List<string> gnames = null;
117+
List<string> gnames;
150118
nsmap.TryGetValue(name, out gnames);
151119
if (gnames?.Count > 0)
152120
{
153121
return gnames[0];
154122
}
155123
return null;
156124
}
125+
126+
private static string GetBasename(string name)
127+
{
128+
int tick = name.IndexOf("`");
129+
if (tick > -1)
130+
{
131+
return name.Substring(0, tick);
132+
}
133+
else
134+
{
135+
return name;
136+
}
137+
}
157138
}
158139
}

src/tests/test_generic.py

+5
Original file line numberDiff line numberDiff line change
@@ -745,3 +745,8 @@ def test_nested_generic_class():
745745
"""Check nested generic classes."""
746746
# TODO NotImplemented
747747
pass
748+
749+
def test_missing_generic_type():
750+
from System.Collections import IList
751+
with pytest.raises(TypeError):
752+
IList[bool]

0 commit comments

Comments
 (0)