Browse Source

Optimize the pass for properties

Signed-off-by: Dimitar Dobrev <dpldobrev@protonmail.com>
pull/1247/head
Dimitar Dobrev 6 years ago
parent
commit
f0e815bf87
  1. 535
      src/Generator/Passes/GetterSetterToPropertyPass.cs
  2. 5
      src/Generator/Passes/MultipleInheritancePass.cs
  3. 2
      src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs
  4. 2
      tests/CSharp/CSharp.Tests.cs
  5. 4
      tests/Common/Common.Tests.cs

535
src/Generator/Passes/GetterSetterToPropertyPass.cs

@ -13,363 +13,310 @@ namespace CppSharp.Passes @@ -13,363 +13,310 @@ namespace CppSharp.Passes
{
public class GetterSetterToPropertyPass : TranslationUnitPass
{
private class PropertyGenerator
static GetterSetterToPropertyPass()
{
private readonly List<Method> getters = new List<Method>();
private readonly List<Method> setters = new List<Method>();
private readonly List<Method> setMethods = new List<Method>();
private readonly List<Method> nonSetters = new List<Method>();
private bool useHeuristics = true;
LoadVerbs();
}
public PropertyGenerator(Class @class, bool useHeuristics)
private static void LoadVerbs()
{
var assembly = Assembly.GetAssembly(typeof(GetterSetterToPropertyPass));
using (var resourceStream = GetResourceStream(assembly))
{
this.useHeuristics = useHeuristics;
foreach (var method in @class.Methods.Where(
m => !m.IsConstructor && !m.IsDestructor && !m.IsOperator && m.IsGenerated &&
!m.ExcludeFromPasses.Contains(typeof(GetterSetterToPropertyPass))))
DistributeMethod(method);
using (var streamReader = new StreamReader(resourceStream))
while (!streamReader.EndOfStream)
verbs.Add(streamReader.ReadLine());
}
}
public void GenerateProperties()
{
GenerateProperties(setters, false);
GenerateProperties(setMethods, true);
private static Stream GetResourceStream(Assembly assembly)
{
var resources = assembly.GetManifestResourceNames();
foreach (Method getter in
from getter in getters
where getter.IsGenerated &&
getter.SynthKind != FunctionSynthKind.ComplementOperator &&
((Class) getter.Namespace).Methods.All(
m => m == getter || !m.IsGenerated || m.Name != getter.Name ||
m.Parameters.Count(p => p.Kind == ParameterKind.Regular) == 0)
select getter)
{
// Make it a read-only property
GenerateProperty(getter.Namespace, getter);
}
}
if (resources.Count() == 0)
throw new Exception("Cannot find embedded verbs data resource.");
private void GenerateProperties(IEnumerable<Method> settersToUse, bool readOnly)
// We are relying on this fact that there is only one resource embedded.
// Before we loaded the resource by name but found out that naming was
// different between different platforms and/or build systems.
return assembly.GetManifestResourceStream(resources[0]);
}
public GetterSetterToPropertyPass()
{
VisitOptions.VisitClassBases = false;
VisitOptions.VisitClassFields = false;
VisitOptions.VisitClassProperties = false;
VisitOptions.VisitClassMethods = false;
VisitOptions.VisitNamespaceEnums = false;
VisitOptions.VisitNamespaceTemplates = false;
VisitOptions.VisitNamespaceTypedefs = false;
VisitOptions.VisitNamespaceEvents = false;
VisitOptions.VisitNamespaceVariables = false;
VisitOptions.VisitFunctionParameters = false;
VisitOptions.VisitTemplateArguments = false;
}
public override bool VisitClassDecl(Class @class)
{
if (!base.VisitClassDecl(@class))
return false;
ProcessProperties(@class, GenerateProperties(@class));
return false;
}
protected virtual HashSet<Property> GenerateProperties(Class @class)
{
var newProperties = new HashSet<Property>();
foreach (var method in @class.Methods.Where(
m => !m.IsConstructor && !m.IsDestructor && !m.IsOperator && m.IsGenerated &&
m.SynthKind != FunctionSynthKind.DefaultValueOverload &&
m.SynthKind != FunctionSynthKind.ComplementOperator &&
!m.ExcludeFromPasses.Contains(typeof(GetterSetterToPropertyPass))))
{
foreach (var setter in settersToUse)
if (IsGetter(method))
{
var type = (Class) setter.Namespace;
var firstWord = GetFirstWord(setter.Name);
string property;
if ((firstWord == "set" || firstWord == "set_") &&
firstWord.Length < setter.Name.Length)
property = setter.Name.Substring(firstWord.Length);
else
property = setter.Name;
var nameBuilder = new StringBuilder(property);
if (char.IsLower(setter.Name[0]))
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]);
string afterSet = nameBuilder.ToString();
var s = setter;
foreach (var getter in nonSetters.Where(m => m.Namespace == type &&
m.ExplicitInterfaceImpl == s.ExplicitInterfaceImpl))
{
var name = GetReadWritePropertyName(getter, afterSet);
if (name == afterSet &&
GetUnderlyingType(getter.OriginalReturnType).Equals(
GetUnderlyingType(setter.Parameters[0].QualifiedType)))
{
Method g = getter;
foreach (var method in type.Methods.Where(m => m != g && m.Name == name))
{
var oldName = method.Name;
method.Name = string.Format("get{0}{1}",
char.ToUpperInvariant(method.Name[0]), method.Name.Substring(1));
Diagnostics.Debug("Method {0}::{1} renamed to {2}", method.Namespace.Name, oldName, method.Name);
}
foreach (var @event in type.Events.Where(e => e.Name == name))
{
var oldName = @event.Name;
@event.Name = string.Format("on{0}{1}",
char.ToUpperInvariant(@event.Name[0]), @event.Name.Substring(1));
Diagnostics.Debug("Event {0}::{1} renamed to {2}", @event.Namespace.Name, oldName, @event.Name);
}
GenerateProperty(name, getter.Namespace, getter, readOnly ? null : setter);
goto next;
}
}
Property baseProperty = type.GetBaseProperty(new Property { Name = afterSet }, getTopmost: true);
if (!type.IsInterface && baseProperty != null && baseProperty.IsVirtual && setter.IsVirtual)
{
bool isReadOnly = baseProperty.SetMethod == null;
var name = GetReadWritePropertyName(baseProperty.GetMethod, afterSet);
GenerateProperty(name, setter.Namespace, baseProperty.GetMethod,
readOnly || isReadOnly ? null : setter);
}
next:
;
string name = GetPropertyName(method.Name);
QualifiedType type = method.OriginalReturnType;
Property property = GetProperty(method, name, type);
property.GetMethod = method;
property.QualifiedType = method.OriginalReturnType;
newProperties.Add(property);
continue;
}
foreach (Method nonSetter in nonSetters)
if (IsSetter(method))
{
Class type = (Class) nonSetter.Namespace;
string name = GetPropertyName(nonSetter.Name);
Property baseProperty = type.GetBaseProperty(new Property { Name = name }, getTopmost: true);
if (!type.IsInterface && baseProperty != null && baseProperty.IsVirtual)
{
bool isReadOnly = baseProperty.SetMethod == null;
if (readOnly == isReadOnly)
{
GenerateProperty(nonSetter.Namespace, nonSetter,
readOnly ? null : baseProperty.SetMethod);
}
}
string name = GetPropertyNameFromSetter(method.Name);
QualifiedType type = method.Parameters.First(p => p.Kind == ParameterKind.Regular).QualifiedType;
Property property = GetProperty(method, name, type);
property.SetMethod = method;
newProperties.Add(property);
}
}
private static string GetReadWritePropertyName(INamedDecl getter, string afterSet)
{
string name = GetPropertyName(getter.Name);
if (name != afterSet && name.StartsWith("is", StringComparison.Ordinal) &&
name != "is")
{
name = char.ToLowerInvariant(name[2]) + name.Substring(3);
}
return name;
}
return newProperties;
}
private static Type GetUnderlyingType(QualifiedType type)
{
TagType tagType = type.Type as TagType;
if (tagType != null)
return type.Type;
// TODO: we should normally check pointer types for const;
// however, there's some bug, probably in the parser, that returns IsConst = false for "const Type& arg"
// so skip the check for the time being
PointerType pointerType = type.Type as PointerType;
return pointerType != null ? pointerType.Pointee : type.Type;
}
private static Property GetProperty(Method method, string name, QualifiedType type)
{
Type underlyingType = GetUnderlyingType(type);
Class @class = (Class) method.Namespace;
Property property = @class.Properties.Find(
p => p.Field == null &&
(p.Name == name ||
(p.GetMethod != null && GetReadWritePropertyName(p.GetMethod, name) == name)) &&
((p.GetMethod != null &&
GetUnderlyingType(p.GetMethod.OriginalReturnType).Equals(underlyingType)) ||
(p.SetMethod != null &&
GetUnderlyingType(p.SetMethod.Parameters[0].QualifiedType).Equals(underlyingType)))) ??
new Property { Name = name, QualifiedType = type };
private static void GenerateProperty(DeclarationContext context, Method getter, Method setter = null)
if (property.Namespace == null)
{
GenerateProperty(GetPropertyName(getter.Name), context, getter, setter);
property.Namespace = method.Namespace;
property.Access = method.Access;
@class.Properties.Add(property);
}
private static void GenerateProperty(string name, DeclarationContext context, Method getter, Method setter)
else
{
var type = (Class) context;
if (type.Properties.Any(p => p.Name == name &&
p.ExplicitInterfaceImpl == getter.ExplicitInterfaceImpl))
return;
var property = new Property
{
Access = getter.Access == AccessSpecifier.Public ||
(setter != null && setter.Access == AccessSpecifier.Public) ?
AccessSpecifier.Public : AccessSpecifier.Protected,
Name = name,
Namespace = type,
QualifiedType = getter.OriginalReturnType,
OriginalNamespace = getter.OriginalNamespace
};
if (getter.IsOverride || (setter != null && setter.IsOverride))
{
var baseVirtualProperty = type.GetBaseProperty(property, getTopmost: true);
if (baseVirtualProperty != null && !baseVirtualProperty.IsVirtual)
{
// the only way the above can happen is if we are generating properties in abstract implementations
// in which case we can have less naming conflicts since the abstract base can also contain non-virtual properties
if (getter.SynthKind == FunctionSynthKind.AbstractImplCall)
return;
throw new Exception(string.Format(
"Base of property {0} is not virtual while the getter is.",
getter.QualifiedOriginalName));
}
if (baseVirtualProperty == null || baseVirtualProperty.SetMethod == null)
setter = null;
}
property.GetMethod = getter;
property.SetMethod = setter;
property.ExplicitInterfaceImpl = getter.ExplicitInterfaceImpl;
if (property.ExplicitInterfaceImpl == null && setter != null)
{
property.ExplicitInterfaceImpl = setter.ExplicitInterfaceImpl;
}
if (getter.Comment != null)
{
property.Comment = CombineComments(getter, setter);
}
type.Properties.Add(property);
getter.GenerationKind = GenerationKind.Internal;
if (setter != null)
setter.GenerationKind = GenerationKind.Internal;
property.Access = (AccessSpecifier) Math.Max(
(int) (property.GetMethod ?? property.SetMethod).Access,
(int) method.Access);
}
private static RawComment CombineComments(Declaration getter, Declaration setter)
property.Name = property.OriginalName = name;
method.GenerationKind = GenerationKind.Internal;
if (method.ExplicitInterfaceImpl != null)
property.ExplicitInterfaceImpl = method.ExplicitInterfaceImpl;
return property;
}
private static void ProcessProperties(Class @class, HashSet<Property> newProperties)
{
foreach (var property in newProperties)
{
var comment = new RawComment
if (property.IsOverride)
{
Kind = getter.Comment.Kind,
BriefText = getter.Comment.BriefText,
Text = getter.Comment.Text
};
if (getter.Comment.FullComment != null)
{
comment.FullComment = new FullComment();
comment.FullComment.Blocks.AddRange(getter.Comment.FullComment.Blocks);
if (getter != setter && setter != null && setter.Comment != null)
Property baseProperty = @class.GetBaseProperty(
new Property { Name = property.Name }, getTopmost: true);
if (baseProperty == null && property.SetMethod != null)
{
comment.BriefText += Environment.NewLine + setter.Comment.BriefText;
comment.Text += Environment.NewLine + setter.Comment.Text;
comment.FullComment.Blocks.AddRange(setter.Comment.FullComment.Blocks);
property.SetMethod.GenerationKind = GenerationKind.Generate;
property.SetMethod = null;
}
else if (property.GetMethod == null && baseProperty.SetMethod != null)
property.GetMethod = baseProperty.GetMethod;
else if (property.SetMethod == null)
property.SetMethod = baseProperty.SetMethod;
}
return comment;
}
private static string GetPropertyName(string name)
{
var firstWord = GetFirstWord(name);
if (Match(firstWord, new[] { "get" }) && name != firstWord &&
!char.IsNumber(name[3]))
if (property.GetMethod == null)
{
if (char.IsLower(name[0]))
{
if (name.Length == 4)
{
return char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture);
}
return char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture) +
name.Substring(4);
}
return name.Substring(3);
if (property.SetMethod != null)
property.SetMethod.GenerationKind = GenerationKind.Generate;
@class.Properties.Remove(property);
continue;
}
return name;
}
private static string GetPropertyNameFromSetter(Method setter)
{
var name = setter.Name.Substring("set".Length);
if (string.IsNullOrEmpty(name))
return name;
if (char.IsLower(setter.Name[0]) && !char.IsLower(name[0]))
return char.ToLowerInvariant(name[0]) + name.Substring(1);
return name;
}
private void DistributeMethod(Method method)
{
Type returnType = method.OriginalReturnType.Type.Desugar();
if ((returnType.IsPrimitiveType(PrimitiveType.Void) ||
returnType.IsPrimitiveType(PrimitiveType.Bool)) &&
method.Parameters.Any(p => p.Kind == ParameterKind.Regular))
foreach (var method in @class.Methods.Where(m => m.IsGenerated && m.Name == property.Name))
{
if (method.Parameters.Count == 1)
setters.Add(method);
else if (method.Parameters.Count > 1)
setMethods.Add(method);
var oldName = method.Name;
method.Name = string.Format("get{0}{1}",
char.ToUpperInvariant(method.Name[0]), method.Name.Substring(1));
Diagnostics.Debug("Method {0}::{1} renamed to {2}", method.Namespace.Name, oldName, method.Name);
}
else
foreach (var @event in @class.Events.Where(e => e.Name == property.Name))
{
if (method.ConvertToProperty || IsGetter(method))
getters.Add(method);
if (method.Parameters.All(p => p.Kind == ParameterKind.IndirectReturnType))
nonSetters.Add(method);
var oldName = @event.Name;
@event.Name = string.Format("on{0}{1}",
char.ToUpperInvariant(@event.Name[0]), @event.Name.Substring(1));
Diagnostics.Debug("Event {0}::{1} renamed to {2}", @event.Namespace.Name, oldName, @event.Name);
}
CombineComments(property);
}
}
private bool IsGetter(Method method)
private static string GetReadWritePropertyName(INamedDecl getter, string afterSet)
{
string name = GetPropertyName(getter.Name);
if (name != afterSet && name.StartsWith("is", StringComparison.Ordinal) &&
name != "is")
{
if (method.IsDestructor ||
(method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void)) ||
method.Parameters.Any(p => p.Kind != ParameterKind.IndirectReturnType))
return false;
var firstWord = GetFirstWord(method.Name);
if (firstWord.Length < method.Name.Length && Match(firstWord, new[] {"get", "is", "has"}))
return true;
name = char.ToLowerInvariant(name[2]) + name.Substring(3);
}
return name;
}
if (useHeuristics && !Match(firstWord, new[] {"to", "new"}) && !verbs.Contains(firstWord))
return true;
private static Type GetUnderlyingType(QualifiedType type)
{
TagType tagType = type.Type as TagType;
if (tagType != null)
return type.Type;
// TODO: we should normally check pointer types for const;
// however, there's some bug, probably in the parser, that returns IsConst = false for "const Type& arg"
// so skip the check for the time being
PointerType pointerType = type.Type as PointerType;
return pointerType != null ? pointerType.Pointee : type.Type;
}
return false;
}
private static void CombineComments(Property property)
{
Method getter = property.GetMethod;
if (getter.Comment == null)
return;
private static bool Match(string prefix, IEnumerable<string> prefixes)
var comment = new RawComment
{
return prefixes.Any(p => prefix == p || prefix == p + '_');
Kind = getter.Comment.Kind,
BriefText = getter.Comment.BriefText,
Text = getter.Comment.Text
};
if (getter.Comment.FullComment != null)
{
comment.FullComment = new FullComment();
comment.FullComment.Blocks.AddRange(getter.Comment.FullComment.Blocks);
Method setter = property.SetMethod;
if (getter != setter && setter?.Comment != null)
{
comment.BriefText += Environment.NewLine + setter.Comment.BriefText;
comment.Text += Environment.NewLine + setter.Comment.Text;
comment.FullComment.Blocks.AddRange(setter.Comment.FullComment.Blocks);
}
}
property.Comment = comment;
}
private static string GetFirstWord(string name)
private static string GetPropertyName(string name)
{
var firstWord = GetFirstWord(name);
if (Match(firstWord, new[] { "get" }) && name != firstWord &&
!char.IsNumber(name[3]))
{
var firstWord = new List<char> { char.ToLowerInvariant(name[0]) };
for (int i = 1; i < name.Length; i++)
if (char.IsLower(name[0]))
{
var c = name[i];
if (char.IsLower(c))
if (name.Length == 4)
{
firstWord.Add(c);
continue;
}
if (c == '_')
{
firstWord.Add(c);
break;
return char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture);
}
if (char.IsUpper(c))
break;
return char.ToLowerInvariant(
name[3]).ToString(CultureInfo.InvariantCulture) +
name.Substring(4);
}
return new string(firstWord.ToArray());
return name.Substring(3);
}
return name;
}
private static readonly HashSet<string> verbs = new HashSet<string>();
static GetterSetterToPropertyPass()
private static string GetPropertyNameFromSetter(string name)
{
LoadVerbs();
}
var nameBuilder = new StringBuilder(name);
string firstWord = GetFirstWord(name);
if (firstWord == "set" || firstWord == "set_")
nameBuilder.Remove(0, firstWord.Length);
if (nameBuilder.Length == 0)
return nameBuilder.ToString();
private static void LoadVerbs()
{
var assembly = Assembly.GetAssembly(typeof(GetterSetterToPropertyPass));
using (var resourceStream = GetResourceStream(assembly))
{
using (var streamReader = new StreamReader(resourceStream))
while (!streamReader.EndOfStream)
verbs.Add(streamReader.ReadLine());
}
nameBuilder.TrimUnderscores();
if (char.IsLower(name[0]) && !char.IsLower(nameBuilder[0]))
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]);
return nameBuilder.ToString();
}
private static Stream GetResourceStream(Assembly assembly)
private bool IsGetter(Method method)
{
var resources = assembly.GetManifestResourceNames();
if (method.IsDestructor ||
method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void) ||
method.Parameters.Any(p => p.Kind != ParameterKind.IndirectReturnType))
return false;
var firstWord = GetFirstWord(method.Name);
if (resources.Count() == 0)
throw new Exception("Cannot find embedded verbs data resource.");
if (firstWord.Length < method.Name.Length &&
Match(firstWord, new[] { "get", "is", "has" }))
return true;
// We are relying on this fact that there is only one resource embedded.
// Before we loaded the resource by name but found out that naming was
// different between different platforms and/or build systems.
return assembly.GetManifestResourceStream(resources[0]);
if (Options.UsePropertyDetectionHeuristics &&
!Match(firstWord, new[] { "to", "new" }) && !verbs.Contains(firstWord))
return true;
return false;
}
public GetterSetterToPropertyPass()
private static bool IsSetter(Method method)
{
VisitOptions.VisitClassBases = false;
VisitOptions.VisitClassFields = false;
VisitOptions.VisitClassProperties = false;
VisitOptions.VisitClassMethods = false;
VisitOptions.VisitNamespaceEnums = false;
VisitOptions.VisitNamespaceTemplates = false;
VisitOptions.VisitNamespaceTypedefs = false;
VisitOptions.VisitNamespaceEvents = false;
VisitOptions.VisitNamespaceVariables = false;
VisitOptions.VisitFunctionParameters = false;
VisitOptions.VisitTemplateArguments = false;
Type returnType = method.OriginalReturnType.Type.Desugar();
return (returnType.IsPrimitiveType(PrimitiveType.Void) ||
returnType.IsPrimitiveType(PrimitiveType.Bool)) &&
method.Parameters.Count(p => p.Kind == ParameterKind.Regular) == 1;
}
public override bool VisitClassDecl(Class @class)
private static bool Match(string prefix, IEnumerable<string> prefixes)
{
if (base.VisitClassDecl(@class))
new PropertyGenerator(@class, Options.UsePropertyDetectionHeuristics).GenerateProperties();
return false;
return prefixes.Any(p => prefix == p || prefix == p + '_');
}
private static string GetFirstWord(string name)
{
var firstWord = new List<char> { char.ToLowerInvariant(name[0]) };
for (int i = 1; i < name.Length; i++)
{
var c = name[i];
if (char.IsLower(c))
{
firstWord.Add(c);
continue;
}
if (c == '_')
{
firstWord.Add(c);
break;
}
if (char.IsUpper(c))
break;
}
return new string(firstWord.ToArray());
}
private static readonly HashSet<string> verbs = new HashSet<string>();
}
}

5
src/Generator/Passes/MultipleInheritancePass.cs

@ -125,6 +125,7 @@ namespace CppSharp.Passes @@ -125,6 +125,7 @@ namespace CppSharp.Passes
QualifiedType = new QualifiedType(new BuiltinType(PrimitiveType.IntPtr)),
GetMethod = new Method
{
Name = Helpers.InstanceIdentifier,
SynthKind = FunctionSynthKind.InterfaceInstance,
Namespace = @interface
}
@ -147,13 +148,15 @@ namespace CppSharp.Passes @@ -147,13 +148,15 @@ namespace CppSharp.Passes
@interface.Declarations.AddRange(@base.Events);
var type = new QualifiedType(new BuiltinType(PrimitiveType.IntPtr));
string pointerAdjustment = "__PointerTo" + @base.Name;
var adjustmentTo = new Property
{
Namespace = @interface,
Name = "__PointerTo" + @base.Name,
Name = pointerAdjustment,
QualifiedType = type,
GetMethod = new Method
{
Name = pointerAdjustment,
SynthKind = FunctionSynthKind.InterfaceInstance,
Namespace = @interface,
ReturnType = type

2
src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs

@ -136,6 +136,8 @@ namespace CppSharp.Passes @@ -136,6 +136,8 @@ namespace CppSharp.Passes
}
}
specializedMethod.Name = specializedMethod.OriginalName;
extensionMethod.Name = extensionMethod.OriginalName;
extensionMethod.OriginalFunction = specializedMethod;
extensionMethod.Kind = CXXMethodKind.Normal;
extensionMethod.IsStatic = true;

2
tests/CSharp/CSharp.Tests.cs

@ -1330,7 +1330,7 @@ public unsafe class CSharpTests : GeneratorTestFixture @@ -1330,7 +1330,7 @@ public unsafe class CSharpTests : GeneratorTestFixture
private class OverrideVirtualTemplate : VirtualTemplate<int>
{
public override int Function() => 10;
public override int Function => 10;
}
[Test]

4
tests/Common/Common.Tests.cs

@ -517,8 +517,8 @@ public class CommonTests : GeneratorTestFixture @@ -517,8 +517,8 @@ public class CommonTests : GeneratorTestFixture
prop.VirtualSetterReturnsBoolean = 45;
Assert.That(prop.VirtualSetterReturnsBoolean, Is.EqualTo(45));
Assert.That(prop.nestedEnum(), Is.EqualTo(5));
Assert.That(prop.nestedEnum(55), Is.EqualTo(55));
Assert.That(prop.nestedEnum, Is.EqualTo(5));
Assert.That(prop.GetNestedEnum(55), Is.EqualTo(55));
Assert.That(prop.Get32Bit, Is.EqualTo(10));
}

Loading…
Cancel
Save