public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { if (!(context.Node is StructDeclarationSyntax dec) || dec.AttributeLists.Count <= 0) { return; } if (UnionDefinition.TryCreate(context, dec, out var def)) { this.Definitions.Add(def); } }
private static void AppendMethod_Set(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); foreach (var member in def.Members) { builder.Append($@" public void Set({member.Type} value) {{ this.{pre_}ValueType = Type.{member.Name}; this.{pre_}{member.Name} = value; }} "); } }
private static void AppendEnum_Type(UnionDefinition def, StringBuilder builder) { string underlyingType; var memberCount = (ulong)def.Members.Count; if (memberCount <= 255) { underlyingType = "byte"; } else if (memberCount <= 32767) { underlyingType = "short"; } else if (memberCount <= 65535) { underlyingType = "ushort"; } else if (memberCount <= 2147483647) { underlyingType = "int"; } else if (memberCount <= 42949672955) { underlyingType = "uint"; } else if (memberCount <= 9223372036854775807) { underlyingType = "long"; } else { underlyingType = "ulong"; } builder.Append($@" public enum Type : {underlyingType} {{"); foreach (var member in def.Members) { builder.Append($@" {member.Name},"); } builder.Append(@" } "); }
private static void AppendMethod_GetUnderlyingType(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); builder.Append($@" public System.Type GetUnderlyingType()"); if (def.Members.Count < 3) { builder.Append(@" {"); foreach (var member in def.Members) { builder.Append($@" if (this.{pre_}ValueType == Type.{member.Name}) return this.{pre_}{member.Name}.GetType(); "); } builder.Append(@" return this.GetType(); } "); } else { builder.Append($@" {{ switch (this.{pre_}ValueType) {{"); foreach (var member in def.Members) { builder.Append($@" case Type.{member.Name}: return this.{pre_}{member.Name}.GetType();"); } builder.Append(@" } return this.GetType(); } "); } }
private static void AppendUsings(UnionDefinition def, StringBuilder unionBuilder, StringBuilder usingBuilder) { foreach (var ns in def.GlobalNamespaces) { usingBuilder.AppendLine($"using {ns};"); } if (def.LocalNamespaces.Count > 0) { unionBuilder.AppendLine(); foreach (var ns in def.LocalNamespaces) { unionBuilder.AppendLine($" using {ns};"); } } }
private static void AppendMethod_GetHashCode(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); builder.Append($@" public override int GetHashCode() {{ var hash = EqualityComparer<Type>.Default.GetHashCode(this.{pre_}ValueType) * -1521134295; "); if (def.Members.Count < 3) { foreach (var member in def.Members) { builder.Append($@" if (this.{pre_}ValueType == Type.{member.Name}) return hash + EqualityComparer<{member.Type}>.Default.GetHashCode(this.{pre_}{member.Name}); "); } builder.Append(@" return hash; } "); } else { builder.Append($@" switch (this.{pre_}ValueType) {{"); foreach (var member in def.Members) { builder.Append($@" case Type.{member.Name}: return hash + EqualityComparer<{member.Type}>.Default.GetHashCode(this.{pre_}{member.Name});"); } builder.Append(@" } return hash; } "); } }
private static void AppendConstructors(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); var last = def.Members.Count - 1; for (var i = 0; i < def.Members.Count; i++) { var member = def.Members[i]; if (i > 0) { builder.AppendLine(); } builder.Append($@" public {def.Name}({member.Type} value) {{ this.{pre_}ValueType = Type.{member.Name}; "); for (var k = 0; k < def.Members.Count; k++) { if (k == i) { continue; } var memberOther = def.Members[k]; builder.Append($@" this.{pre_}{memberOther.Name} = default;"); } builder.Append($@" this.{pre_}{member.Name} = value; }}"); if (i == last) { builder.AppendLine(); } } }
public void Execute(GeneratorExecutionContext context, UnionDefinition def) { var usingBuilder = new StringBuilder($@"using {GeneratorConfig.Namespace}; "); var unionBuilder = new StringBuilder($@" namespace {def.Namespace} {{ using System.Runtime.InteropServices; "); AppendUsings(def, unionBuilder, usingBuilder); unionBuilder.Append($@" [StructLayout(LayoutKind.Explicit, Pack = 1)] public partial struct {def.Name} {{"); AppendEnum_Type(def, unionBuilder); AppendProp_ValueType(def, unionBuilder); AppendProps(def, unionBuilder); AppendConstructors(def, unionBuilder); AppendConstructorTypeParam(def, unionBuilder); if (!def.IsReadOnly) { AppendMethod_Set(def, unionBuilder); } AppendMethod_TryGet(def, unionBuilder); AppendMethod_GetUnderlyingType(def, unionBuilder); AppendMethod_GetHashCode(def, unionBuilder); AppendMethod_Equals(def, unionBuilder); AppendMethod_ToString(def, unionBuilder); AppendOperator_Implicit(def, unionBuilder); unionBuilder.Append(@" } }"); usingBuilder.AppendLine(unionBuilder.ToString()); context.AddSource($"Union_{def.Name}.cs", SourceText.From(usingBuilder.ToString(), Encoding.UTF8)); }
private static void AppendConstructorTypeParam(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); builder.Append($@" public {def.Name}(Type type) {{ this.{pre_}ValueType = type; "); foreach (var member in def.Members) { builder.Append($@" this.{pre_}{member.Name} = default;"); } builder.Append(@" } "); }
private static void AppendMethod_ToString(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); builder.Append(@" public override string ToString() {"); if (def.Members.Count < 3) { foreach (var member in def.Members) { builder.Append($@" if (this.{pre_}ValueType == Type.{member.Name}) return this.{pre_}{member.Name}.ToString(); "); } builder.Append(@" return string.Empty; }"); } else { builder.Append($@" switch (this.{pre_}ValueType) {{"); foreach (var member in def.Members) { builder.Append($@" case Type.{member.Name}: return this.{pre_}{member.Name}.ToString();"); } builder.Append(@" } return string.Empty; }"); } }
private static void AppendMethod_TryGet(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); foreach (var member in def.Members) { builder.Append($@" public bool TryGet(out {member.Type} value) {{ if (this.{pre_}ValueType != Type.{member.Name}) {{ value = default; return false; }} value = this.{pre_}{member.Name}; return true; }} "); } }
private static void AppendProp_ValueType(UnionDefinition def, StringBuilder builder) { if (def.IsReadOnly && def.InvalidValueAccess == InvalidValueAccess.Allow) { builder.Append(@" [FieldOffset(0)] public readonly Type ValueType; "); } else { var readonlyKeyword = def.IsReadOnly ? "readonly " : string.Empty; builder.Append($@" [FieldOffset(0)] private {readonlyKeyword}Type m_ValueType; public Type ValueType => this.m_ValueType; "); } }
private static void AppendOperator_Implicit(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); var last = def.Members.Count - 1; switch (def.InvalidValueAccess) { case InvalidValueAccess.ThrowException: { for (var i = 0; i < def.Members.Count; i++) { var member = def.Members[i]; if (i <= 0) { builder.AppendLine(); } builder.Append($@" public static implicit operator {def.Name}({member.Type} value) => new {def.Name}(value); "); builder.Append($@" public static implicit operator {member.Type}(in {def.Name} value) {{ if (value.{pre_}ValueType == Type.{member.Name}) return value.{pre_}{member.Name}; throw new InvalidValueAccessException($""Cannot implicitly convert underlying type '{{value.GetUnderlyingType().GetNiceFullName()}}' to '{member.Type}'""); }}"); if (i < last) { builder.AppendLine(); } } break; } case InvalidValueAccess.ReturnDefault: { for (var i = 0; i < def.Members.Count; i++) { var member = def.Members[i]; if (i <= 0) { builder.AppendLine(); } builder.Append($@" public static implicit operator {def.Name}({member.Type} value) => new {def.Name}(value); "); builder.Append($@" public static implicit operator {member.Type}(in {def.Name} value) {{ if (value.{pre_}ValueType == Type.{member.Name}) return value.{pre_}{member.Name}; return default; }}"); if (i < last) { builder.AppendLine(); } } break; } default: { for (var i = 0; i < def.Members.Count; i++) { var member = def.Members[i]; if (i <= 0) { builder.AppendLine(); } builder.Append($@" public static implicit operator {def.Name}({member.Type} value) => new {def.Name}(value); "); builder.Append($@" public static implicit operator {member.Type}(in {def.Name} value) => value.{pre_}{member.Name};"); if (i < last) { builder.AppendLine(); } } break; } } }
public static bool TryCreate(GeneratorSyntaxContext context, StructDeclarationSyntax dec, out UnionOperatorDefinition def) { if (!UnionDefinition.TryCreate(context, dec, out var unionDef)) { def = default; return(false); } var attributes = new List <AttributeSyntax>(); foreach (var attribList in dec.AttributeLists) { foreach (var attrib in attribList.Attributes) { var name = attrib.Name.ToString(); if (string.Equals(name, "UnionOperator") || string.Equals(name, "UnionOperatorAttribute")) { attributes.Add(attrib); break; } } } if (attributes.Count < 1) { def = default; return(false); } var operators = new List <Operator>(); var operatorSet = new HashSet <Op>(); var operatorList = new List <Op>(); foreach (var attribute in attributes) { var operandTypeHandling = OperandTypeHandling.Implicit; foreach (var arg in attribute.ArgumentList.Arguments) { if (!(arg.Expression is MemberAccessExpressionSyntax memberAccess)) { continue; } var memberName = memberAccess.Expression.ToString(); if (string.Equals(memberName, nameof(Op))) { if (Enum.TryParse <Op>(memberAccess.Name.ToString(), true, out var value) && !operatorSet.Contains(value)) { operatorSet.Add(value); operatorList.Add(value); } } else if (string.Equals(memberName, nameof(OperandTypeHandling))) { if (Enum.TryParse <OperandTypeHandling>(memberAccess.Name.ToString(), true, out var value)) { operandTypeHandling = value; } } } foreach (var op in operatorList) { operators.Add(new Operator(op, operandTypeHandling)); } operatorList.Clear(); } if (operators.Count < 1) { def = default; return(false); } def = new UnionOperatorDefinition { UnionDefinition = unionDef }; for (var i = 0; i < operators.Count; i++) { def.Operators.Add(operators[i]); } return(true); }
private static void AppendProps(UnionDefinition def, StringBuilder builder) { if (def.IsReadOnly && def.InvalidValueAccess == InvalidValueAccess.Allow) { foreach (var member in def.Members) { builder.Append($@" [FieldOffset(1)] public readonly {member.Type} {member.Name}; "); } return; } var readonlyKeyword = def.IsReadOnly ? "readonly " : string.Empty; foreach (var member in def.Members) { builder.Append($@" [FieldOffset(1)] private {readonlyKeyword}{member.Type} m_{member.Name}; "); } switch (def.InvalidValueAccess) { case InvalidValueAccess.ThrowException: { foreach (var member in def.Members) { builder.Append($@" public {member.Type} {member.Name} {{ get {{ if (this.m_ValueType == Type.{member.Name}) return this.m_{member.Name}; throw new InvalidValueAccessException($""Cannot convert underlying type '{{GetUnderlyingType().GetNiceFullName()}}' to '{member.Type}'""); }} }} "); } break; } case InvalidValueAccess.ReturnDefault: { foreach (var member in def.Members) { builder.Append($@" public {member.Type} {member.Name} {{ get {{ if (this.m_ValueType == Type.{member.Name}) return this.m_{member.Name}; return default; }} }} "); } break; } default: { foreach (var member in def.Members) { builder.Append($@" public {member.Type} {member.Name} => this.m_{member.Name}; "); } break; } } }
public static bool TryCreate(GeneratorSyntaxContext context, StructDeclarationSyntax dec, out UnionDefinition def) { AttributeSyntax attribute = null; foreach (var attribList in dec.AttributeLists) { foreach (var attrib in attribList.Attributes) { var name = attrib.Name.ToString(); if (string.Equals(name, "Union") || string.Equals(name, "UnionAttribute")) { attribute = attrib; break; } } } if (attribute == null || attribute.ArgumentList == null || attribute.ArgumentList.Arguments.Count < 1) { def = default; return(false); } TypeOfExpressionSyntax typeOf = null; MemberAccessExpressionSyntax memberAccess = null; foreach (var arg in attribute.ArgumentList.Arguments) { if (arg.Expression is TypeOfExpressionSyntax typeOfSyntax) { typeOf = typeOfSyntax; } else if (arg.Expression is MemberAccessExpressionSyntax memberAccessSyntax) { memberAccess = memberAccessSyntax; } } if (typeOf == null || !(typeOf.Type is TupleTypeSyntax tuple) || tuple.Elements.Count <= 0) { def = default; return(false); } def = new UnionDefinition { Name = dec.Identifier.ToString(), InvalidValueAccess = InvalidValueAccess.Allow }; def.GetGlobalNamespaces(dec); def.GetLocalNamespaces(dec); foreach (var element in tuple.Elements) { GetName(context.SemanticModel, element, out var type, out var name); def.Members.Add(new MemberDefinition(type, name)); } foreach (var keyword in dec.Modifiers) { if (string.Equals(keyword.ToString(), "readonly")) { def.IsReadOnly = true; } } if (memberAccess != null && string.Equals(memberAccess.Expression.ToString(), nameof(InvalidValueAccess))) { if (Enum.TryParse <InvalidValueAccess>(memberAccess.Name.ToString(), true, out var value)) { def.InvalidValueAccess = value; } } return(true); }
private static void AppendMethod_Equals(UnionDefinition def, StringBuilder builder) { var pre_ = def.GetMemberPrefix(); builder.Append($@" public override bool Equals(object obj) => obj is {def.Name} other && Equals(in this, in other); public bool Equals({def.Name} other) => Equals(in this, in other); public bool Equals(in {def.Name} other) => Equals(in this, in other); "); builder.Append($@" public static bool Equals(in {def.Name} a, in {def.Name} b)"); if (def.Members.Count < 3) { builder.Append($@" {{ if (a.{pre_}ValueType != b.{pre_}ValueType) return false; "); foreach (var member in def.Members) { builder.Append($@" if (a.{pre_}ValueType == Type.{member.Name}) return EqualityComparer<{member.Type}>.Default.Equals(a.{pre_}{member.Name}, b.{pre_}{member.Name}); "); } builder.Append(@" return false; } "); } else { builder.Append($@" {{ if (a.{pre_}ValueType != b.{pre_}ValueType) return false; switch (a.{pre_}ValueType) {{"); foreach (var member in def.Members) { builder.Append($@" case Type.{member.Name}: return EqualityComparer<{member.Type}>.Default.Equals(a.{pre_}{member.Name}, b.{pre_}{member.Name});"); } builder.Append(@" } return false; } "); } builder.Append($@" public static bool operator ==(in {def.Name} left, in {def.Name} right) => Equals(in left, in right); public static bool operator !=(in {def.Name} left, in {def.Name} right) => !Equals(in left, in right); "); }