mirror of https://github.com/mono/CppSharp.git
c-sharpdotnetmonobindingsbridgecclangcpluspluscppsharpglueinteropparserparsingpinvokeswigsyntax-treevisitorsxamarinxamarin-bindings
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
15 KiB
387 lines
15 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Reflection; |
|
using System.Text; |
|
using CppSharp.AST; |
|
using CppSharp.AST.Extensions; |
|
using Type = CppSharp.AST.Type; |
|
|
|
namespace CppSharp.Passes |
|
{ |
|
public class GetterSetterToPropertyPass : TranslationUnitPass |
|
{ |
|
static GetterSetterToPropertyPass() |
|
{ |
|
LoadVerbs(); |
|
} |
|
|
|
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()); |
|
} |
|
} |
|
|
|
private static Stream GetResourceStream(Assembly assembly) |
|
{ |
|
var resources = assembly.GetManifestResourceNames(); |
|
|
|
if (resources.Count() == 0) |
|
throw new Exception("Cannot find embedded verbs data resource."); |
|
|
|
// 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 List<Property> GetProperties(Class @class) => |
|
new List<Property>(); |
|
|
|
protected IEnumerable<Property> GenerateProperties(Class @class) |
|
{ |
|
List<Property> properties = GetProperties(@class); |
|
foreach (Method method in @class.Methods.Where( |
|
m => !m.IsConstructor && !m.IsDestructor && !m.IsOperator && m.IsGenerated && |
|
(properties.All(p => p.GetMethod != m && p.SetMethod != m) || |
|
m.OriginalFunction != null) && |
|
m.SynthKind != FunctionSynthKind.DefaultValueOverload && |
|
m.SynthKind != FunctionSynthKind.ComplementOperator && |
|
m.SynthKind != FunctionSynthKind.FieldAcessor && |
|
!m.ExcludeFromPasses.Contains(typeof(GetterSetterToPropertyPass)))) |
|
{ |
|
if (IsGetter(method)) |
|
{ |
|
string name = GetPropertyName(method.Name); |
|
GetProperty(properties, method, name, method.OriginalReturnType); |
|
continue; |
|
} |
|
if (IsSetter(method)) |
|
{ |
|
string name = GetPropertyNameFromSetter(method.Name); |
|
QualifiedType type = method.Parameters.First( |
|
p => p.Kind == ParameterKind.Regular).QualifiedType; |
|
GetProperty(properties, method, name, type, true); |
|
} |
|
} |
|
|
|
return CleanUp(@class, properties); |
|
} |
|
|
|
private IEnumerable<Property> CleanUp(Class @class, List<Property> properties) |
|
{ |
|
if (!Options.UsePropertyDetectionHeuristics) |
|
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" })) |
|
continue; |
|
|
|
if (Match(firstWord, new[] { "to", "new", "on" }) || |
|
verbs.Contains(firstWord)) |
|
{ |
|
property.GetMethod.GenerationKind = GenerationKind.Generate; |
|
@class.Properties.Remove(property); |
|
properties.RemoveAt(i); |
|
} |
|
} |
|
|
|
return properties; |
|
} |
|
|
|
private static void GetProperty(List<Property> properties, Method method, |
|
string name, QualifiedType type, bool isSetter = false) |
|
{ |
|
Type underlyingType = GetUnderlyingType(type); |
|
Class @class = (Class) method.Namespace; |
|
|
|
Property property = properties.Find( |
|
p => p.Field == null && |
|
((!isSetter && p.SetMethod?.IsStatic == method.IsStatic) || |
|
(isSetter && p.GetMethod?.IsStatic == method.IsStatic)) && |
|
((p.HasGetter && GetUnderlyingType( |
|
p.GetMethod.OriginalReturnType).Equals(underlyingType)) || |
|
(p.HasSetter && GetUnderlyingType( |
|
p.SetMethod.Parameters[0].QualifiedType).Equals(underlyingType))) && |
|
Match(p, name)); |
|
|
|
if (property == null) |
|
properties.Add(property = new Property { Name = name, QualifiedType = type }); |
|
|
|
method.AssociatedDeclaration = property; |
|
|
|
if (isSetter) |
|
property.SetMethod = method; |
|
else |
|
{ |
|
property.GetMethod = method; |
|
property.QualifiedType = method.OriginalReturnType; |
|
} |
|
|
|
property.Access = (AccessSpecifier) Math.Max( |
|
(int) (property.GetMethod ?? property.SetMethod).Access, |
|
(int) method.Access); |
|
|
|
if (method.ExplicitInterfaceImpl != null) |
|
property.ExplicitInterfaceImpl = method.ExplicitInterfaceImpl; |
|
} |
|
|
|
private static bool Match(Property property, string name) |
|
{ |
|
if (string.IsNullOrEmpty(name)) |
|
return false; |
|
|
|
if (property.Name == name) |
|
return true; |
|
|
|
if (property.Name == RemovePrefix(name)) |
|
return true; |
|
|
|
if (RemovePrefix(property.Name) == name) |
|
{ |
|
property.Name = property.OriginalName = name; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
private static string RemovePrefix(string identifier) |
|
{ |
|
if (string.IsNullOrEmpty(identifier)) |
|
return identifier; |
|
|
|
string name = GetPropertyName(identifier); |
|
return name.StartsWith("is", StringComparison.Ordinal) && name != "is" ? |
|
char.ToLowerInvariant(name[2]) + name.Substring(3) : name; |
|
} |
|
|
|
private static void ProcessProperties(Class @class, IEnumerable<Property> properties) |
|
{ |
|
foreach (Property property in properties) |
|
{ |
|
ProcessOverridden(@class, property); |
|
|
|
if (!property.HasGetter) |
|
continue; |
|
if (!property.HasSetter && |
|
@class.GetOverloads(property.GetMethod).Any( |
|
m => m != property.GetMethod && !m.Ignore)) |
|
continue; |
|
|
|
Property conflict = properties.LastOrDefault( |
|
p => p.Name == property.Name && p != property && |
|
p.ExplicitInterfaceImpl == property.ExplicitInterfaceImpl); |
|
if (conflict?.GetMethod != null) |
|
conflict.GetMethod = null; |
|
|
|
property.GetMethod.GenerationKind = GenerationKind.Internal; |
|
if (property.SetMethod != null && |
|
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); |
|
} |
|
} |
|
|
|
private static void ProcessOverridden(Class @class, Property property) |
|
{ |
|
if (!property.IsOverride) |
|
return; |
|
|
|
Property baseProperty = @class.GetBaseProperty(property); |
|
if (baseProperty == null) |
|
{ |
|
if (property.HasSetter) |
|
property.SetMethod = null; |
|
else |
|
property.GetMethod = null; |
|
} |
|
else if (!property.HasGetter && baseProperty.HasSetter) |
|
property.GetMethod = baseProperty.GetMethod; |
|
else if (!property.HasSetter || !baseProperty.HasSetter) |
|
property.SetMethod = baseProperty.SetMethod; |
|
} |
|
|
|
private static void RenameConflictingMethods(Class @class, Property property) |
|
{ |
|
foreach (var method in @class.Methods.Where( |
|
m => m.IsGenerated && m.Name == property.Name)) |
|
{ |
|
var oldName = method.Name; |
|
method.Name = $@"get{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 @class.Events.Where( |
|
e => e.Name == property.Name)) |
|
{ |
|
var oldName = @event.Name; |
|
@event.Name = $@"on{char.ToUpperInvariant(@event.Name[0])}{ |
|
@event.Name.Substring(1)}"; |
|
Diagnostics.Debug("Event {0}::{1} renamed to {2}", |
|
@event.Namespace.Name, oldName, @event.Name); |
|
} |
|
} |
|
|
|
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 void CombineComments(Property property) |
|
{ |
|
Method getter = property.GetMethod; |
|
if (getter.Comment == null) |
|
return; |
|
|
|
var comment = new RawComment |
|
{ |
|
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 GetPropertyName(string name) |
|
{ |
|
var firstWord = GetFirstWord(name); |
|
if (Match(firstWord, new[] { "get" }) && |
|
(string.Compare(name, firstWord, StringComparison.InvariantCultureIgnoreCase) != 0) && |
|
!char.IsNumber(name[3])) |
|
{ |
|
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); |
|
} |
|
return name; |
|
} |
|
|
|
private static string GetPropertyNameFromSetter(string name) |
|
{ |
|
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(); |
|
|
|
nameBuilder.TrimUnderscores(); |
|
if (char.IsLower(name[0]) && !char.IsLower(nameBuilder[0])) |
|
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]); |
|
return nameBuilder.ToString(); |
|
} |
|
|
|
private bool IsGetter(Method method) => |
|
!method.IsDestructor && |
|
!method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void) && |
|
!method.Parameters.Any(p => p.Kind != ParameterKind.IndirectReturnType); |
|
|
|
private static bool IsSetter(Method method) |
|
{ |
|
Type returnType = method.OriginalReturnType.Type.Desugar(); |
|
return (returnType.IsPrimitiveType(PrimitiveType.Void) || |
|
returnType.IsPrimitiveType(PrimitiveType.Bool)) && |
|
method.Parameters.Count(p => p.Kind == ParameterKind.Regular) == 1; |
|
} |
|
|
|
private static bool Match(string prefix, IEnumerable<string> prefixes) |
|
{ |
|
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>(); |
|
} |
|
}
|
|
|