Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework property handling #1714

Merged
merged 3 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/CLI/CLI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using CppSharp.Generators;
using CppSharp.Passes;
using Mono.Options;

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

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

static void GetPropertyMode(string mode, List<string> errorMessages)
{
switch (mode.ToLower())
{
case "all":
options.PropertyMode = PropertyDetectionMode.All;
return;
case "none":
options.PropertyMode = PropertyDetectionMode.None;
return;
case "dictionary":
options.PropertyMode = PropertyDetectionMode.Dictionary;
return;
case "keywords":
options.PropertyMode = PropertyDetectionMode.Keywords;
return;
}

errorMessages.Add($"Unknown property detection mode: {mode}. Defaulting to {options.PropertyMode}");
}

static void PrintErrorMessages(List<string> errorMessages)
{
foreach (string m in errorMessages)
Expand Down
1 change: 1 addition & 0 deletions src/CLI/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public void Setup(Driver driver)

var driverOptions = driver.Options;
driverOptions.GeneratorKind = options.Kind;
driverOptions.PropertyDetectionMode = options.PropertyMode;
var module = driverOptions.AddModule(options.OutputFileName);

if (!string.IsNullOrEmpty(options.InputLibraryName))
Expand Down
3 changes: 3 additions & 0 deletions src/CLI/Options.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using CppSharp.Generators;
using CppSharp.Passes;

namespace CppSharp
{
Expand Down Expand Up @@ -43,6 +44,8 @@ class Options

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

public PropertyDetectionMode PropertyMode { get; set; } = PropertyDetectionMode.Keywords;

public bool CheckSymbols { get; set; }

public bool UnityBuild { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions src/Generator/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ public bool GenerateSingleCSharpFile
/// </summary>
public HashSet<string> ExplicitlyPatchedVirtualFunctions { get; }

public PropertyDetectionMode PropertyDetectionMode { get; set; } = PropertyDetectionMode.Dictionary;

[Obsolete("Use PropertyDetectionMode instead")]
public bool UsePropertyDetectionHeuristics { get; set; } = true;

/// <summary>
Expand Down
100 changes: 74 additions & 26 deletions src/Generator/Passes/GetterSetterToPropertyPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@

namespace CppSharp.Passes
{
/// <summary>
/// This is used by GetterSetterToPropertyPass to decide how to process
/// getter/setter class methods into properties.
/// </summary>
public enum PropertyDetectionMode
{
/// <summary>
/// No methods are converted to properties.
/// </summary>
None,
/// <summary>
/// All compatible methods are converted to properties.
/// </summary>
All,
/// <summary>
/// Only methods starting with certain keyword are converted to properties.
/// Right now we consider getter methods starting with "get", "is" and "has".
/// </summary>
Keywords,
/// <summary>
/// Heuristics based mode that uses english dictionary words to decide
/// if a getter method is an action and thus not to be considered as a
/// property.
/// </summary>
Dictionary
}

public class GetterSetterToPropertyPass : TranslationUnitPass
{
static GetterSetterToPropertyPass()
Expand Down Expand Up @@ -44,6 +71,9 @@ public GetterSetterToPropertyPass()

public override bool VisitClassDecl(Class @class)
{
if (Options.PropertyDetectionMode == PropertyDetectionMode.None)
return false;

if (!base.VisitClassDecl(@class))
return false;

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

private IEnumerable<Property> CleanUp(Class @class, List<Property> properties)
{
if (!Options.UsePropertyDetectionHeuristics)
#pragma warning disable CS0618
if (!Options.UsePropertyDetectionHeuristics ||
#pragma warning restore CS0618
Options.PropertyDetectionMode == PropertyDetectionMode.All)
return properties;

for (int i = properties.Count - 1; i >= 0; i--)
{
Property property = properties[i];
if (property.HasSetter || property.IsExplicitlyGenerated)
continue;

string firstWord = GetFirstWord(property.GetMethod.Name);
if (firstWord.Length < property.GetMethod.Name.Length &&
Match(firstWord, new[] { "get", "is", "has" }))
var property = properties[i];
if (KeepProperty(property))
continue;

if (Match(firstWord, new[] { "to", "new", "on" }) ||
Verbs.Contains(firstWord))
{
property.GetMethod.GenerationKind = GenerationKind.Generate;
@class.Properties.Remove(property);
properties.RemoveAt(i);
}
property.GetMethod.GenerationKind = GenerationKind.Generate;
@class.Properties.Remove(property);
properties.RemoveAt(i);
}

return properties;
}

public virtual bool KeepProperty(Property property)
{
if (property.HasSetter || property.IsExplicitlyGenerated)
return true;

var firstWord = GetFirstWord(property.GetMethod.Name);
var isKeyword = firstWord.Length < property.GetMethod.Name.Length &&
Match(firstWord, new[] {"get", "is", "has"});

switch (Options.PropertyDetectionMode)
{
case PropertyDetectionMode.Keywords:
return isKeyword;
case PropertyDetectionMode.Dictionary:
var isAction = Match(firstWord, new[] {"to", "new", "on"}) || Verbs.Contains(firstWord);
return isKeyword || !isAction;
default:
return false;
}
}

private static void CreateOrUpdateProperty(List<Property> properties, Method method,
string name, QualifiedType type, bool isSetter = false)
{
string NormalizeName(string name)
{
return string.IsNullOrEmpty(name) ?
name : string.Concat(char.ToLowerInvariant(name[0]), name.Substring(1));
}

var normalizedName = NormalizeName(name);

Type underlyingType = GetUnderlyingType(type);
Property property = properties.Find(
p => p.Field == null &&
Expand All @@ -124,10 +177,10 @@ private static void CreateOrUpdateProperty(List<Property> properties, Method met
p.GetMethod.OriginalReturnType).Equals(underlyingType)) ||
(p.HasSetter && GetUnderlyingType(
p.SetMethod.Parameters[0].QualifiedType).Equals(underlyingType))) &&
Match(p, name));
Match(p, normalizedName));

if (property == null)
properties.Add(property = new Property { Name = name, QualifiedType = type });
properties.Add(property = new Property { Name = normalizedName, QualifiedType = type });

method.AssociatedDeclaration = property;

Expand Down Expand Up @@ -201,7 +254,9 @@ private static void ProcessProperties(Class @class, IEnumerable<Property> proper
property.SetMethod.OriginalReturnType.Type.Desugar().IsPrimitiveType(PrimitiveType.Void))
property.SetMethod.GenerationKind = GenerationKind.Internal;
property.Namespace = @class;

@class.Properties.Add(property);

RenameConflictingMethods(@class, property);
CombineComments(property);
}
Expand Down Expand Up @@ -294,14 +349,8 @@ private static string GetPropertyName(string name)
(string.Compare(name, firstWord, StringComparison.InvariantCultureIgnoreCase) == 0) ||
char.IsNumber(name[3])) return name;

if (name.Length == 4)
{
return char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture);
}

return string.Concat(char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture), name.AsSpan(4));
var rest = (name.Length == 4) ? string.Empty : name.Substring(4);
return string.Concat(name[3], rest);
}

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

nameBuilder.TrimUnderscores();
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]);
return nameBuilder.ToString();
}

Expand Down
Loading