Skip to content

Commit c5815fb

Browse files
authored
[generator] Be smarter about when members with same names need "new" (#567)
Today when a generated member has the same name as a method on `System.Object` we generate the method with a `new` in order to hide the base member. However, we only compare via the method name and not the method signature, meaning we add more `new` than is necessary. For example this creates a warning because `new` is not needed: // Org.Json.JSONArray.cs public new virtual unsafe string ToString (int indentSpaces) { … } warning CS0109: The member 'JSONArray.ToString(int)' does not hide an accessible member. The new keyword is not required. These are the methods from `System.Object` that we currently check against: * `bool Equals (object)` * `static bool Equals (object, object)` * `static bool ReferenceEquals (object, object)` * `int GetHashCode ()` * `Type GetType ()` * `string ToString ()` Additionally, all properties named `Handle` must get `new`. With this change we now are more explicit about what methods we are checking against so we can ensure that parameters and `static`-ness match as well. This eliminates 87 warnings from the API-29 `Mono.Android.dll` build.
1 parent bfc0273 commit c5815fb

File tree

3 files changed

+171
-7
lines changed

3 files changed

+171
-7
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using MonoDroid.Generation;
7+
using NUnit.Framework;
8+
9+
namespace generatortests
10+
{
11+
[TestFixture]
12+
public class GenBaseTests
13+
{
14+
CodeGenerationOptions options = new CodeGenerationOptions ();
15+
16+
[Test]
17+
public void PropertyRequiresNew ()
18+
{
19+
var c = SupportTypeBuilder.CreateClassWithProperty ("MyClass", "java.myClass", "Handle", "int", options);
20+
Assert.True (c.RequiresNew (c.Properties.First ()));
21+
22+
c.Properties.First ().Name = "GetHashCode";
23+
Assert.True (c.RequiresNew (c.Properties.First ()));
24+
25+
c.Properties.First ().Name = "GetType";
26+
Assert.True (c.RequiresNew (c.Properties.First ()));
27+
28+
c.Properties.First ().Name = "ToString";
29+
Assert.True (c.RequiresNew (c.Properties.First ()));
30+
31+
c.Properties.First ().Name = "Equals";
32+
Assert.True (c.RequiresNew (c.Properties.First ()));
33+
34+
c.Properties.First ().Name = "ReferenceEquals";
35+
Assert.True (c.RequiresNew (c.Properties.First ()));
36+
37+
c.Properties.First ().Name = "Handle2";
38+
Assert.False (c.RequiresNew (c.Properties.First ()));
39+
}
40+
41+
[Test]
42+
public void ToStringRequiresNew () => TestParameterlessMethods ("ToString");
43+
44+
[Test]
45+
public void GetTypeRequiresNew () => TestParameterlessMethods ("GetType");
46+
47+
[Test]
48+
public void GetHashCodeRequiresNew () => TestParameterlessMethods ("GetHashCode");
49+
50+
[Test]
51+
public void StaticEqualsCodeRequiresNew () => TestStaticMethodsWithTwoParameters ("Equals");
52+
53+
[Test]
54+
public void ReferenceEqualsEqualsCodeRequiresNew () => TestStaticMethodsWithTwoParameters ("ReferenceEquals");
55+
56+
[Test]
57+
public void HandleAlwaysRequiresNew ()
58+
{
59+
// The same name as a property always requires new, no matter the parameters
60+
var c = SupportTypeBuilder.CreateClass ("java.myClass", options);
61+
var m = SupportTypeBuilder.CreateMethod (c, "Handle", options);
62+
63+
// Yes
64+
Assert.True (c.RequiresNew (m.Name, m));
65+
66+
// Yes, even with parameters
67+
m.Parameters.Add (new Parameter ("value", "int", "int", false));
68+
69+
Assert.True (c.RequiresNew (m.Name, m));
70+
}
71+
72+
[Test]
73+
public void TestEqualsMethodsWithOneParameter ()
74+
{
75+
var c = SupportTypeBuilder.CreateClass ("java.myClass", options);
76+
var m = SupportTypeBuilder.CreateMethod (c, "Equals", options, "void", false, false);
77+
78+
// No because 0 parameters
79+
Assert.False (c.RequiresNew (m.Name, m));
80+
81+
var p0 = new Parameter ("p0", "object", "object", false);
82+
m.Parameters.Add (p0);
83+
84+
// Yes
85+
Assert.True (c.RequiresNew (m.Name, m));
86+
87+
// No because parameter is wrong type
88+
var p1 = new Parameter ("p1", "string", "string", false);
89+
m = SupportTypeBuilder.CreateMethod (c, "Equals", options, "void", true, false, p1);
90+
91+
Assert.False (c.RequiresNew (m.Name, m));
92+
}
93+
94+
void TestParameterlessMethods (string name)
95+
{
96+
var c = SupportTypeBuilder.CreateClass ("java.myClass", options);
97+
var m = SupportTypeBuilder.CreateMethod (c, name, options);
98+
99+
// Yes
100+
Assert.True (c.RequiresNew (m.Name, m));
101+
102+
// No because > 0 parameters
103+
m.Parameters.Add (new Parameter ("value", "int", "int", false));
104+
105+
Assert.False (c.RequiresNew (m.Name, m));
106+
}
107+
108+
void TestStaticMethodsWithTwoParameters (string name)
109+
{
110+
var c = SupportTypeBuilder.CreateClass ("java.myClass", options);
111+
112+
var p0 = new Parameter ("p0", "object", "object", false);
113+
var p1 = new Parameter ("p1", "object", "object", false);
114+
var m = SupportTypeBuilder.CreateMethod (c, name, options, "void", true, false, p0, p1);
115+
116+
// Yes
117+
Assert.True (c.RequiresNew (m.Name, m));
118+
119+
// No because != 2 parameters
120+
m.Parameters.Add (new Parameter ("value", "int", "int", false));
121+
Assert.False (c.RequiresNew (m.Name, m));
122+
123+
// No because parameter is wrong type
124+
var p2 = new Parameter ("p1", "string", "string", false);
125+
m = SupportTypeBuilder.CreateMethod (c, name, options, "void", true, false, p0, p2);
126+
127+
Assert.False (c.RequiresNew (m.Name, m));
128+
}
129+
}
130+
}

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ public void WriteMethodAbstractDeclaration (Method method, string indent, Interf
11191119
WriteMethodCustomAttributes (method, indent);
11201120
writer.WriteLine ("{0}{1}{2} abstract {3} {4} ({5});",
11211121
indent,
1122-
impl.RequiresNew (method.Name) ? "new " : "",
1122+
impl.RequiresNew (method.Name, method) ? "new " : "",
11231123
method.Visibility,
11241124
opt.GetOutputName (method.RetVal.FullName),
11251125
name,
@@ -1367,7 +1367,7 @@ public void WriteMethod (Method method, string indent, GenBase type, bool genera
13671367
seal = string.Empty;
13681368
}
13691369

1370-
if ((string.IsNullOrEmpty (virt_ov) || virt_ov == " virtual") && type.RequiresNew (method.AdjustedName)) {
1370+
if ((string.IsNullOrEmpty (virt_ov) || virt_ov == " virtual") && type.RequiresNew (method.AdjustedName, method)) {
13711371
virt_ov = " new" + virt_ov;
13721372
}
13731373
string ret = opt.GetOutputName (method.RetVal.FullName);
@@ -1460,7 +1460,7 @@ public void WriteProperty (Property property, GenBase gen, string indent, bool w
14601460
force_override = true;
14611461

14621462
string decl_name = property.AdjustedName;
1463-
string needNew = gen.RequiresNew (decl_name) ? " new" : "";
1463+
string needNew = gen.RequiresNew (property) ? " new" : "";
14641464
string virtual_override = String.Empty;
14651465
bool is_virtual = property.Getter.IsVirtual && (property.Setter == null || property.Setter.IsVirtual);
14661466
if (with_callbacks && is_virtual) {
@@ -1537,7 +1537,7 @@ public void WritePropertyAbstractDeclaration (Property property, string indent,
15371537
string abstract_name = property.AdjustedName;
15381538
string visibility = property.Getter.RetVal.IsGeneric ? "protected" : property.Getter.Visibility;
15391539
if (!overrides) {
1540-
requiresNew = gen.RequiresNew (abstract_name);
1540+
requiresNew = gen.RequiresNew (property);
15411541
WritePropertyCallbacks (property, indent, gen, abstract_name);
15421542
}
15431543
writer.WriteLine ("{0}{1}{2} abstract{3} {4} {5} {{",

tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,10 +727,44 @@ public string [] PreCallback (CodeGenerationOptions opt, string var_name, bool o
727727

728728
public string RawVisibility => support.Visibility;
729729

730-
public bool RequiresNew (string memberName)
730+
public bool RequiresNew (Property property)
731+
{
732+
switch (property.AdjustedName.ToLowerInvariant ()) {
733+
case "handle":
734+
case "gethashcode":
735+
case "gettype":
736+
case "tostring":
737+
case "equals":
738+
case "referenceequals":
739+
return true;
740+
}
741+
742+
return IsThrowable () && ThrowableRequiresNew.Contains (property.AdjustedName);
743+
}
744+
745+
public bool RequiresNew (string memberName, Method method)
731746
{
732-
if (ObjectRequiresNew.Contains (memberName))
733-
return true;
747+
switch (memberName.ToLowerInvariant ()) {
748+
case "handle":
749+
// The same name as a property always requires new, no matter the parameters
750+
return true;
751+
case "gethashcode":
752+
case "gettype":
753+
case "tostring":
754+
return method.Parameters.Count == 0;
755+
case "equals":
756+
if (method.Parameters.Count == 1 && method.Parameters.All (p => p.Type == "object"))
757+
return true;
758+
if (method.Parameters.Count == 2 && method.Parameters.All (p => p.Type == "object"))
759+
return true;
760+
761+
break;
762+
case "referenceequals":
763+
if (method.Parameters.Count == 2 && method.Parameters.All (p => p.Type == "object"))
764+
return true;
765+
766+
break;
767+
}
734768

735769
return IsThrowable () && ThrowableRequiresNew.Contains (memberName);
736770
}

0 commit comments

Comments
 (0)