Skip to content

Commit 2d8bfe9

Browse files
committed
[generator] Fix invalid parsing of complex generic types.
1 parent 5136ef9 commit 2d8bfe9

File tree

3 files changed

+157
-32
lines changed

3 files changed

+157
-32
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using MonoDroid.Generation;
3+
using NUnit.Framework;
4+
5+
namespace generatortests
6+
{
7+
[TestFixture]
8+
public class CodeGenerationOptionsTests
9+
{
10+
[Test]
11+
public void GetOutputNameUseGlobal ()
12+
{
13+
var opt = new CodeGenerationOptions { UseGlobal = true };
14+
15+
Assert.AreEqual (string.Empty, opt.GetOutputName (string.Empty));
16+
Assert.AreEqual ("int", opt.GetOutputName ("int"));
17+
Assert.AreEqual ("void", opt.GetOutputName ("void"));
18+
Assert.AreEqual ("void", opt.GetOutputName ("System.Void"));
19+
Assert.AreEqual ("params int[]", opt.GetOutputName ("params int[]"));
20+
Assert.AreEqual ("params global::System.Object[]", opt.GetOutputName ("params System.Object[]"));
21+
22+
Assert.AreEqual ("global::System.Collections.Generic.List<string[]>",
23+
opt.GetOutputName ("System.Collections.Generic.List<string[]>"));
24+
25+
Assert.AreEqual ("global::System.Collections.Generic.Dictionary<string, string>",
26+
opt.GetOutputName ("System.Collections.Generic.Dictionary<string, string>"));
27+
28+
Assert.AreEqual ("global::System.Collections.Generic.List<global::System.Collections.Generic.List<string>>",
29+
opt.GetOutputName ("System.Collections.Generic.List<System.Collections.Generic.List<string>>"));
30+
31+
Assert.AreEqual ("global::System.Collections.Generic.List<global::System.Collections.Generic.Dictionary<string, global::System.Collections.Generic.Dictionary<global::System.Object, global::System.Object>>>",
32+
opt.GetOutputName ("System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<System.Object, System.Object>>>"));
33+
34+
Assert.AreEqual ("global::System.Collections.Generic.IList<global::Kotlin.Pair>",
35+
opt.GetOutputName ("System.Collections.Generic.IList<Kotlin.Pair>"));
36+
37+
Assert.AreEqual ("global::System.Collections.Generic.IDictionary<string, global::System.Collections.Generic.IList<global::Kotlin.Pair>>",
38+
opt.GetOutputName ("System.Collections.Generic.IDictionary<string, System.Collections.Generic.IList<Kotlin.Pair>>"));
39+
40+
Assert.AreEqual ("global::System.Collections.Generic.IDictionary<global::System.Collections.Generic.IList<string>, global::Kotlin.Pair>",
41+
opt.GetOutputName ("System.Collections.Generic.IDictionary<System.Collections.Generic.IList<string>, Kotlin.Pair>"));
42+
43+
Assert.AreEqual ("global::System.Collections.Generic.IDictionary<global::System.Collections.Generic.IList<string>, global::System.Collections.Generic.IList<global::Kotlin.Pair>>",
44+
opt.GetOutputName ("System.Collections.Generic.IDictionary<System.Collections.Generic.IList<string>, System.Collections.Generic.IList<Kotlin.Pair>>"));
45+
}
46+
}
47+
}

tools/generator/CodeGenerationOptions.cs

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -212,42 +212,20 @@ internal IEnumerable<string> GetJniMarshalDelegates ()
212212
return jni_marshal_delegates;
213213
}
214214

215-
public string GetOutputName (string s)
215+
public string GetOutputName (string type)
216216
{
217-
if (s == "System.Void")
217+
// Handle a few special cases
218+
if (type == "System.Void")
218219
return "void";
219-
if (s.StartsWith ("params "))
220-
return "params " + GetOutputName (s.Substring ("params ".Length));
221-
if (s.StartsWith ("global::"))
220+
if (type.StartsWith ("params "))
221+
return "params " + GetOutputName (type.Substring ("params ".Length));
222+
if (type.StartsWith ("global::"))
222223
Report.LogCodedError (Report.ErrorUnexpectedGlobal);
223224
if (!UseGlobal)
224-
return s;
225-
int idx = s.IndexOf ('<');
226-
if (idx < 0) {
227-
if (s.IndexOf ('.') < 0)
228-
return s; // hack, to prevent things like global::int
229-
return "global::" + s;
230-
}
231-
int idx2 = s.LastIndexOf ('>');
232-
string sub = s.Substring (idx + 1, idx2 - idx - 1);
233-
var typeParams = new List<string> ();
234-
while (true) {
235-
int idx3 = sub.IndexOf ('<');
236-
int idx4 = sub.IndexOf (',');
237-
if (idx4 < 0) {
238-
typeParams.Add (GetOutputName (sub));
239-
break;
240-
} else if (idx3 < 0 || idx4 < idx3) { // more than one type params.
241-
typeParams.Add (GetOutputName (sub.Substring (0, idx4)));
242-
if (idx4 + 1 == sub.Length)
243-
break;
244-
sub = sub.Substring (idx4 + 1).Trim ();
245-
} else {
246-
typeParams.Add (GetOutputName (sub));
247-
break;
248-
}
249-
}
250-
return GetOutputName (s.Substring (0, idx)) + '<' + String.Join (", ", typeParams.ToArray ()) + '>';
225+
return type;
226+
227+
// Add "global::" in front of types
228+
return ParsedType.Parse (type).ToString (UseGlobal);
251229
}
252230

253231
public string GetSafeIdentifier (string name)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace MonoDroid.Generation
5+
{
6+
// Parses a type string into its type and optionally its generic type arguments
7+
// Ex: Dictionary<string, List<string>>
8+
// -> Type: Dictionary
9+
// - GenericArguments:
10+
// - Type: string
11+
// - Type: List
12+
// - GenericArguments:
13+
// - Type: string
14+
public class ParsedType
15+
{
16+
public string Type { get; set; }
17+
public List<ParsedType> GenericArguments { get; } = new List<ParsedType> ();
18+
public bool HasGenerics => GenericArguments.Count > 0;
19+
20+
ParsedType () { }
21+
22+
public static ParsedType Parse (string type)
23+
{
24+
var less_than = type.IndexOf ('<');
25+
26+
// No generics
27+
if (less_than < 0)
28+
return new ParsedType { Type = type };
29+
30+
var type_string = type.Substring (0, less_than);
31+
var type_args = type.Substring (less_than + 1, type.Length - less_than - 2);
32+
33+
var parsed_args = ParseTypeList (type_args);
34+
35+
var t = new ParsedType { Type = type_string };
36+
37+
foreach (var p in parsed_args)
38+
t.GenericArguments.Add (Parse (p));
39+
40+
return t;
41+
}
42+
43+
public override string ToString ()
44+
{
45+
return ToString (false);
46+
}
47+
48+
public string ToString (bool useGlobal = false)
49+
{
50+
var type = (useGlobal && Type.IndexOf ('.') >= 0 ? "global::" : string.Empty) + Type;
51+
52+
if (!HasGenerics)
53+
return type;
54+
55+
return $"{type}<{string.Join (", ", GenericArguments.Select (p => p.ToString (useGlobal)))}>";
56+
}
57+
58+
static List<string> ParseTypeList (string type)
59+
{
60+
var list = new List<string> ();
61+
62+
// Only one type
63+
if (type.IndexOf (',') < 0) {
64+
list.Add (type);
65+
return list;
66+
}
67+
68+
// Remove any whitespace
69+
type = type.Replace (" ", "");
70+
71+
var start = 0;
72+
var counter = -1;
73+
var depth = 0;
74+
75+
while (++counter < type.Length) {
76+
if (type [counter] == '<') {
77+
depth++;
78+
continue;
79+
}
80+
81+
if (type [counter] == '>') {
82+
depth--;
83+
continue;
84+
}
85+
86+
// This is a list separator, add the previous type
87+
if (depth == 0 && type [counter] == ',') {
88+
list.Add (type.Substring (start, counter - start));
89+
start = counter + 1;
90+
continue;
91+
}
92+
}
93+
94+
// Add the final type
95+
list.Add (type.Substring (start, counter - start));
96+
97+
return list;
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)