Skip to content

Commit 45d6b32

Browse files
authored
Rework property handling (#1714)
* Rewrite property detection logic to be more configurable. * Add new property modes to the CLI driver. * Refactor property handling in GetterSetterToProperty.
1 parent de0b0ba commit 45d6b32

File tree

5 files changed

+104
-26
lines changed

5 files changed

+104
-26
lines changed

src/CLI/CLI.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Linq;
55
using CppSharp.Generators;
6+
using CppSharp.Passes;
67
using Mono.Options;
78

89
namespace CppSharp
@@ -34,6 +35,7 @@ static bool ParseCommandLineArgs(string[] args, List<string> errorMessages, ref
3435
optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux' or 'emscripten'", p => { GetDestinationPlatform(p, errorMessages); });
3536
optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64' or 'wasm32' or 'wasm64'", a => { GetDestinationArchitecture(a, errorMessages); });
3637
optionSet.Add("prefix=", "sets a string prefix to the names of generated files", a => { options.Prefix = a; });
38+
optionSet.Add("property=", "the property detection mode to use: 'all', 'none' or 'keywords' or 'heuristics'", p => { GetPropertyMode(p, errorMessages); });
3739

3840
optionSet.Add("exceptions", "enables support for C++ exceptions in the parser", v => { options.EnableExceptions = true; });
3941
optionSet.Add("rtti", "enables support for C++ RTTI in the parser", v => { options.EnableRTTI = true; });
@@ -269,6 +271,27 @@ public static void GetDestinationArchitecture(string architecture, List<string>
269271
Defaulting to {options.Architecture}");
270272
}
271273

274+
static void GetPropertyMode(string mode, List<string> errorMessages)
275+
{
276+
switch (mode.ToLower())
277+
{
278+
case "all":
279+
options.PropertyMode = PropertyDetectionMode.All;
280+
return;
281+
case "none":
282+
options.PropertyMode = PropertyDetectionMode.None;
283+
return;
284+
case "dictionary":
285+
options.PropertyMode = PropertyDetectionMode.Dictionary;
286+
return;
287+
case "keywords":
288+
options.PropertyMode = PropertyDetectionMode.Keywords;
289+
return;
290+
}
291+
292+
errorMessages.Add($"Unknown property detection mode: {mode}. Defaulting to {options.PropertyMode}");
293+
}
294+
272295
static void PrintErrorMessages(List<string> errorMessages)
273296
{
274297
foreach (string m in errorMessages)

src/CLI/Generator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public void Setup(Driver driver)
129129

130130
var driverOptions = driver.Options;
131131
driverOptions.GeneratorKind = options.Kind;
132+
driverOptions.PropertyDetectionMode = options.PropertyMode;
132133
var module = driverOptions.AddModule(options.OutputFileName);
133134

134135
if (!string.IsNullOrEmpty(options.InputLibraryName))

src/CLI/Options.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using CppSharp.Generators;
3+
using CppSharp.Passes;
34

45
namespace CppSharp
56
{
@@ -43,6 +44,8 @@ class Options
4344

4445
public GeneratorKind Kind { get; set; } = GeneratorKind.CSharp;
4546

47+
public PropertyDetectionMode PropertyMode { get; set; } = PropertyDetectionMode.Keywords;
48+
4649
public bool CheckSymbols { get; set; }
4750

4851
public bool UnityBuild { get; set; }

src/Generator/Options.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ public bool GenerateSingleCSharpFile
247247
/// </summary>
248248
public HashSet<string> ExplicitlyPatchedVirtualFunctions { get; }
249249

250+
public PropertyDetectionMode PropertyDetectionMode { get; set; } = PropertyDetectionMode.Dictionary;
251+
252+
[Obsolete("Use PropertyDetectionMode instead")]
250253
public bool UsePropertyDetectionHeuristics { get; set; } = true;
251254

252255
/// <summary>

src/Generator/Passes/GetterSetterToPropertyPass.cs

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@
1111

1212
namespace CppSharp.Passes
1313
{
14+
/// <summary>
15+
/// This is used by GetterSetterToPropertyPass to decide how to process
16+
/// getter/setter class methods into properties.
17+
/// </summary>
18+
public enum PropertyDetectionMode
19+
{
20+
/// <summary>
21+
/// No methods are converted to properties.
22+
/// </summary>
23+
None,
24+
/// <summary>
25+
/// All compatible methods are converted to properties.
26+
/// </summary>
27+
All,
28+
/// <summary>
29+
/// Only methods starting with certain keyword are converted to properties.
30+
/// Right now we consider getter methods starting with "get", "is" and "has".
31+
/// </summary>
32+
Keywords,
33+
/// <summary>
34+
/// Heuristics based mode that uses english dictionary words to decide
35+
/// if a getter method is an action and thus not to be considered as a
36+
/// property.
37+
/// </summary>
38+
Dictionary
39+
}
40+
1441
public class GetterSetterToPropertyPass : TranslationUnitPass
1542
{
1643
static GetterSetterToPropertyPass()
@@ -44,6 +71,9 @@ public GetterSetterToPropertyPass()
4471

4572
public override bool VisitClassDecl(Class @class)
4673
{
74+
if (Options.PropertyDetectionMode == PropertyDetectionMode.None)
75+
return false;
76+
4777
if (!base.VisitClassDecl(@class))
4878
return false;
4979

@@ -86,35 +116,58 @@ protected IEnumerable<Property> GenerateProperties(Class @class)
86116

87117
private IEnumerable<Property> CleanUp(Class @class, List<Property> properties)
88118
{
89-
if (!Options.UsePropertyDetectionHeuristics)
119+
#pragma warning disable CS0618
120+
if (!Options.UsePropertyDetectionHeuristics ||
121+
#pragma warning restore CS0618
122+
Options.PropertyDetectionMode == PropertyDetectionMode.All)
90123
return properties;
91124

92125
for (int i = properties.Count - 1; i >= 0; i--)
93126
{
94-
Property property = properties[i];
95-
if (property.HasSetter || property.IsExplicitlyGenerated)
96-
continue;
97-
98-
string firstWord = GetFirstWord(property.GetMethod.Name);
99-
if (firstWord.Length < property.GetMethod.Name.Length &&
100-
Match(firstWord, new[] { "get", "is", "has" }))
127+
var property = properties[i];
128+
if (KeepProperty(property))
101129
continue;
102130

103-
if (Match(firstWord, new[] { "to", "new", "on" }) ||
104-
Verbs.Contains(firstWord))
105-
{
106-
property.GetMethod.GenerationKind = GenerationKind.Generate;
107-
@class.Properties.Remove(property);
108-
properties.RemoveAt(i);
109-
}
131+
property.GetMethod.GenerationKind = GenerationKind.Generate;
132+
@class.Properties.Remove(property);
133+
properties.RemoveAt(i);
110134
}
111135

112136
return properties;
113137
}
114138

139+
public virtual bool KeepProperty(Property property)
140+
{
141+
if (property.HasSetter || property.IsExplicitlyGenerated)
142+
return true;
143+
144+
var firstWord = GetFirstWord(property.GetMethod.Name);
145+
var isKeyword = firstWord.Length < property.GetMethod.Name.Length &&
146+
Match(firstWord, new[] {"get", "is", "has"});
147+
148+
switch (Options.PropertyDetectionMode)
149+
{
150+
case PropertyDetectionMode.Keywords:
151+
return isKeyword;
152+
case PropertyDetectionMode.Dictionary:
153+
var isAction = Match(firstWord, new[] {"to", "new", "on"}) || Verbs.Contains(firstWord);
154+
return isKeyword || !isAction;
155+
default:
156+
return false;
157+
}
158+
}
159+
115160
private static void CreateOrUpdateProperty(List<Property> properties, Method method,
116161
string name, QualifiedType type, bool isSetter = false)
117162
{
163+
string NormalizeName(string name)
164+
{
165+
return string.IsNullOrEmpty(name) ?
166+
name : string.Concat(char.ToLowerInvariant(name[0]), name.Substring(1));
167+
}
168+
169+
var normalizedName = NormalizeName(name);
170+
118171
Type underlyingType = GetUnderlyingType(type);
119172
Property property = properties.Find(
120173
p => p.Field == null &&
@@ -124,10 +177,10 @@ private static void CreateOrUpdateProperty(List<Property> properties, Method met
124177
p.GetMethod.OriginalReturnType).Equals(underlyingType)) ||
125178
(p.HasSetter && GetUnderlyingType(
126179
p.SetMethod.Parameters[0].QualifiedType).Equals(underlyingType))) &&
127-
Match(p, name));
180+
Match(p, normalizedName));
128181

129182
if (property == null)
130-
properties.Add(property = new Property { Name = name, QualifiedType = type });
183+
properties.Add(property = new Property { Name = normalizedName, QualifiedType = type });
131184

132185
method.AssociatedDeclaration = property;
133186

@@ -201,7 +254,9 @@ private static void ProcessProperties(Class @class, IEnumerable<Property> proper
201254
property.SetMethod.OriginalReturnType.Type.Desugar().IsPrimitiveType(PrimitiveType.Void))
202255
property.SetMethod.GenerationKind = GenerationKind.Internal;
203256
property.Namespace = @class;
257+
204258
@class.Properties.Add(property);
259+
205260
RenameConflictingMethods(@class, property);
206261
CombineComments(property);
207262
}
@@ -294,14 +349,8 @@ private static string GetPropertyName(string name)
294349
(string.Compare(name, firstWord, StringComparison.InvariantCultureIgnoreCase) == 0) ||
295350
char.IsNumber(name[3])) return name;
296351

297-
if (name.Length == 4)
298-
{
299-
return char.ToLowerInvariant(
300-
name[3]).ToString(CultureInfo.InvariantCulture);
301-
}
302-
303-
return string.Concat(char.ToLowerInvariant(
304-
name[3]).ToString(CultureInfo.InvariantCulture), name.AsSpan(4));
352+
var rest = (name.Length == 4) ? string.Empty : name.Substring(4);
353+
return string.Concat(name[3], rest);
305354
}
306355

307356
private static string GetPropertyNameFromSetter(string name)
@@ -314,7 +363,6 @@ private static string GetPropertyNameFromSetter(string name)
314363
return nameBuilder.ToString();
315364

316365
nameBuilder.TrimUnderscores();
317-
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]);
318366
return nameBuilder.ToString();
319367
}
320368

0 commit comments

Comments
 (0)