diff --git a/src/Generator/Passes/GetterSetterToPropertyPass.cs b/src/Generator/Passes/GetterSetterToPropertyPass.cs index bb1017dd50..f002da8f60 100644 --- a/src/Generator/Passes/GetterSetterToPropertyPass.cs +++ b/src/Generator/Passes/GetterSetterToPropertyPass.cs @@ -13,363 +13,310 @@ namespace CppSharp.Passes { public class GetterSetterToPropertyPass : TranslationUnitPass { - private class PropertyGenerator + static GetterSetterToPropertyPass() { - private readonly List getters = new List(); - private readonly List setters = new List(); - private readonly List setMethods = new List(); - private readonly List nonSetters = new List(); - 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 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 GenerateProperties(Class @class) + { + var newProperties = new HashSet(); + 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 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 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.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 verbs = new HashSet(); - - 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 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.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 verbs = new HashSet(); } } diff --git a/src/Generator/Passes/MultipleInheritancePass.cs b/src/Generator/Passes/MultipleInheritancePass.cs index a836caa67d..12fa69afa6 100644 --- a/src/Generator/Passes/MultipleInheritancePass.cs +++ b/src/Generator/Passes/MultipleInheritancePass.cs @@ -125,6 +125,7 @@ where property.IsDeclared QualifiedType = new QualifiedType(new BuiltinType(PrimitiveType.IntPtr)), GetMethod = new Method { + Name = Helpers.InstanceIdentifier, SynthKind = FunctionSynthKind.InterfaceInstance, Namespace = @interface } @@ -147,13 +148,15 @@ where property.IsDeclared @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 diff --git a/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs b/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs index 328b4a28d9..e681abc5f3 100644 --- a/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs +++ b/src/Generator/Passes/SpecializationMethodsWithDependentPointersPass.cs @@ -136,6 +136,8 @@ private static Method GetExtensionMethodForDependentPointer(Method specializedMe } } + specializedMethod.Name = specializedMethod.OriginalName; + extensionMethod.Name = extensionMethod.OriginalName; extensionMethod.OriginalFunction = specializedMethod; extensionMethod.Kind = CXXMethodKind.Normal; extensionMethod.IsStatic = true; diff --git a/tests/CSharp/CSharp.Tests.cs b/tests/CSharp/CSharp.Tests.cs index 42f13a7d2d..8dd9ed3c33 100644 --- a/tests/CSharp/CSharp.Tests.cs +++ b/tests/CSharp/CSharp.Tests.cs @@ -1330,7 +1330,7 @@ public class Inter : SimpleInterface private class OverrideVirtualTemplate : VirtualTemplate { - public override int Function() => 10; + public override int Function => 10; } [Test] diff --git a/tests/Common/Common.Tests.cs b/tests/Common/Common.Tests.cs index c5f90274bc..72ee7fc4bc 100644 --- a/tests/Common/Common.Tests.cs +++ b/tests/Common/Common.Tests.cs @@ -517,8 +517,8 @@ public void TestProperties() 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)); }