diff --git a/macros/StructuralEquality.n b/macros/StructuralEquality.n index 56f672409f..782427e32d 100644 --- a/macros/StructuralEquality.n +++ b/macros/StructuralEquality.n @@ -1,4 +1,4 @@ -using Nemerle; +using Nemerle; using Nemerle.Collections; using Nemerle.Compiler; using Nemerle.Compiler.Parsetree; @@ -10,495 +10,587 @@ using System.Collections.Generic; namespace Nemerle.Extensions { - /// Implements Equals and related methods, using the concept of - /// http://everything2.com/title/structural+equality - [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Class, Inherited = false, AllowMultiple = false)] - public macro StructuralEquality(tb : TypeBuilder, params _options : list[PExpr]) - { - StructuralEqualityImpl.RunBeforeInheritance(tb); - } - - [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)] - public macro StructuralEquality(tb : TypeBuilder, params options : list[PExpr]) - { - StructuralEqualityImpl.RunWithTypedMembers(tb, options); - } - - [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Field, Inherited = false, AllowMultiple = false)] - public macro EqualsIgnore(tb : TypeBuilder, field : ParsedField) - { - StructuralEqualityImpl.Ignore(tb, field); - } - - [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property, Inherited = false, AllowMultiple = false)] - public macro EqualsIgnore(tb : TypeBuilder, prop : ParsedProperty) - { - StructuralEqualityImpl.Ignore(tb, prop); - } - - module StructuralEqualityImpl - { - public IndexOf[T](this source : list[T], elem : T) : int +/// Implements Equals and related methods, using the concept of +/// http://everything2.com/title/structural+equality +[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Class, Inherited = false, AllowMultiple = false)] + public macro StructuralEquality(tb : TypeBuilder, params _options : list[PExpr]) { - def loop(source, elem, index) - { - match (source) - { - | head :: _ when head.Equals(elem) => index - | _ :: tail => loop(tail, elem, index + 1) - | _ => -1 - } - } - - loop(source, elem, 0) + StructuralEqualityImpl.RunBeforeInheritance(tb, _options); } - - public NOfTypeRev[TIn, TFilter](this source : list[TIn]) : list[TFilter] + +[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)] + public macro StructuralEquality(tb : TypeBuilder, params options : list[PExpr]) { - def loop(listIn : list[TIn], listOut : list[TFilter]) - { - match (listIn) - { - | head :: tail => - match (head) - { - | x is TFilter => loop(tail, x :: listOut) - | _ => loop(tail, listOut) - } - - | _ => listOut - } - } - - loop(source, []) + StructuralEqualityImpl.RunWithTypedMembers(tb, options); + } + +[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Field, Inherited = false, AllowMultiple = false)] + public macro EqualsIgnore(tb : TypeBuilder, field : ParsedField) + { + StructuralEqualityImpl.Ignore(tb, field.Name, true, StructuralEqualityImpl.IgnoredFieldsLabel); + } + +[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property, Inherited = false, AllowMultiple = false)] + public macro EqualsIgnore(tb : TypeBuilder, prop : ParsedProperty) + { + StructuralEqualityImpl.Ignore(tb, prop.Name,true, StructuralEqualityImpl.IgnoredPropertiesLabel); + } + +[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Field, Inherited = false, AllowMultiple = false)] + public macro EqualsNotIgnore(tb : TypeBuilder, field : ParsedField) + { + // assert2(false) ; + StructuralEqualityImpl.Ignore(tb, field.Name, false, StructuralEqualityImpl.IgnoredFieldsLabel); + } + +[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property, Inherited = false, AllowMultiple = false)] + public macro EqualsNotIgnore(tb : TypeBuilder, prop : ParsedProperty) + { + StructuralEqualityImpl.Ignore(tb, prop.Name, false, StructuralEqualityImpl.IgnoredPropertiesLabel); } + module StructuralEqualityImpl + { + // used as keys in UserData - public IgnoredFieldsLabel : string = "StructuralEquality.IgnoredFields"; - public IgnoredPropertiesLabel : string = "StructuralEquality.IgnoredProperties"; - public IsEquatableImplementedLabel : string = "StructuralEquality.IsEquatableImplemented"; - public IsStructuralEquatableImplementedLabel : string = "StructuralEquality.IsStructuralEquatableImplemented"; + static public IgnoredFieldsLabel : string = "StructuralEquality.IgnoredFields"; + static public IgnoredPropertiesLabel : string = "StructuralEquality.IgnoredProperties"; + static public IsEquatableImplementedLabel : string = "StructuralEquality.IsEquatableImplemented"; + static public IsStructuralEquatableImplementedLabel : string = "StructuralEquality.IsStructuralEquatableImplemented"; + static public StructuralEquatableTypeInfoLabel : string = "StructuralEquality.StructuralEquatableTypeInfoLabel"; // implements interfaces - public RunBeforeInheritance(tb : TypeBuilder) : void - { - def type = GetTypeName(tb); - - if (tb.IsValueType || tb.IsSealed) - tb.Define(<[ decl: - private EqualsImpl($("other" : dyn) : $type) : bool - { - } ]>); - else - tb.Define(<[ decl: - protected virtual EqualsImpl($("other" : dyn) : $type) : bool - { - } ]>); - - when (tb.Ast is TopDeclaration.Variant) - foreach (vo in tb.GetVariantOptions()) - vo.Ast.AddCustomAttribute(<[ StructuralEquality ]>); - - // Nemerle doesn't build if Tuples from stdlib are changed - unless (tb.IsVariantOption) - { - tb.AddImplementedInterface(<[ System.IEquatable.[$type] ]>); - tb.UserData.Add(IsEquatableImplementedLabel, true); + public RunBeforeInheritance(tb : TypeBuilder, options_expr : list[PExpr]) : void + { + def type = GetTypeName(tb); - // only .NET 4.0+ supports this - when (tb.Manager.NameTree.LookupExactType("System.Collections.IStructuralEquatable").IsSome) + if (tb.IsValueType || tb.IsSealed) + tb.Define(<[ decl: + private EqualsImpl($("other" : dyn) : $type) : bool + { + } ]>); + else + tb.Define(<[ decl: + protected virtual EqualsImpl($("other" : dyn) : $type) : bool { - tb.AddImplementedInterface(<[ System.Collections.IStructuralEquatable ]>); - tb.UserData.Add(IsStructuralEquatableImplementedLabel, true); + } ]>); + + when (tb.Ast is TopDeclaration.Variant) + foreach (vo in tb.GetVariantOptions()) + vo.Ast.AddCustomAttribute(<[ StructuralEquality( ..$options_expr ) ]>); + + // Nemerle doesn't build if Tuples from stdlib are changed + unless (tb.IsVariantOption) + { + tb.AddImplementedInterface(<[ System.IEquatable.[$type] ]>); + tb.UserData.Add(IsEquatableImplementedLabel, true); + + // only .NET 4.0+ supports this + when (tb.Manager.NameTree.LookupExactType("System.Collections.IStructuralEquatable") is Some(iface)) + { + tb.AddImplementedInterface(<[ System.Collections.IStructuralEquatable ]>); + tb.UserData.Add(IsStructuralEquatableImplementedLabel, true); + tb.UserData.Add(StructuralEquatableTypeInfoLabel, iface); + } + } } - } - } // parses options, defines methods - public RunWithTypedMembers(tb : TypeBuilder, options_expr : list[PExpr]) : void - { - //assert2(false); - def options = SEOptions.Parse(options_expr); - - def get_relevant_fields(tb) - { - def all_fields = tb.GetFields(BindingFlags.Public %| - BindingFlags.NonPublic %| - BindingFlags.Instance %| - BindingFlags.DeclaredOnly); - - // retrieve all ignored fields - def ignore_fields = - if (tb.UserData.Contains(IgnoredFieldsLabel)) - tb.UserData[IgnoredFieldsLabel] :> List[string] - else - List(); + public RunWithTypedMembers(tb : TypeBuilder, options_expr : list[PExpr]) : void + { + + + // assert2(false); + def options = SEOptions.Parse(options_expr); + + + def get_relevant_fields(tb) + { + def all_fields = tb.GetFields(BindingFlags.Public %| + BindingFlags.NonPublic %| + BindingFlags.Instance %| + BindingFlags.DeclaredOnly); + + //retrieve all ignored fields + def ignore_fields = + if (tb.UserData.Contains(IgnoredFieldsLabel)) + tb.UserData[IgnoredFieldsLabel]:>Hashtable.[string,bool] else Hashtable(); + // ignored properties - when (tb.UserData.Contains(IgnoredPropertiesLabel)) - { - def prop_list = tb.UserData[IgnoredPropertiesLabel] :> List[string]; + when (tb.UserData.Contains(IgnoredPropertiesLabel)) + { + def prop_list = tb.UserData[IgnoredPropertiesLabel] :>Hashtable.[string,bool]; + + foreach (prop in prop_list) + { + match (tb.LookupMember(prop.Key).Find(x => x is IProperty)) + { + | Some(builder is PropertyBuilder) => + match (builder.AutoPropertyField) + { + | Some(field) => ignore_fields.Add(field.Name,true) + | _ => Message.Warning(builder.Location, $"$prop is not an autoproperty. No need to use EqualsIgnore") + } + + | _ => Message.Error($"Property $prop not found.") + } + } + } - foreach (prop in prop_list) - { - match (tb.LookupMember(prop).Find(x => x is IProperty)) - { - | Some(builder is PropertyBuilder) => - match (builder.AutoPropertyField) + +// ignore_fields.AddRange(options.IgnoreFields); +// ignore_fields.Sort(); + + // remove ignored fields and return result + + def FindHashName(x) + { + match(ignore_fields.Get(x), options.IgnoreFields.Get(x)) { - | Some(field) => ignore_fields.Add(field.Name) - | _ => Message.Warning(builder.Location, $"$prop is not an autoproperty. No need to use EqualsIgnore") + | (Some(true), _) => false; + | (Some(false), _) => true; + | (None, Some(true)) => false; + | (None, Some(false)) => true; + | (None,None) => ! options.AllIgnore; } + } - | _ => Message.Error($"Property $prop not found.") + all_fields.Filter(x => + FindHashName(x.Name)||(tb.IsVariantOption&&FindHashName(tb.Name+"."+x.Name))); } - } - } - ignore_fields.AddRange(options.IgnoreFields); - ignore_fields.Sort(); + // fields that are not ignored when evaluating structural equality + //assert2(false); - // remove ignored fields and return result - all_fields.Filter(x => ignore_fields.BinarySearch(x.Name) < 0); - } - - // fields that are not ignored when evaluating structural equality - def relevant_fields = get_relevant_fields(tb); - - // true if strict type equality is needed, i. e. no subtypes are allowed; - def typecheck_needed = !tb.IsSealed && !tb.IsVariantOption && !tb.IsValueType && options.CheckTypes; - //assert2(false); - DefineEquality(tb, relevant_fields, typecheck_needed); - DefineHashCode(tb, relevant_fields); - DefineOperators(tb); - DefineStructural(tb); - } + //when(!tb.UserData.Contains(IgnoredFieldsLabel)) + //tb.UserData.Add(IgnoredFieldsLabel,List.[string]()); - // adds a field to ignore list - public Ignore(tb : TypeBuilder, field : ClassMember.Field) : void - { - unless (tb.UserData.Contains(IgnoredFieldsLabel)) - tb.UserData.Add(IgnoredFieldsLabel, List.[string]()); + //def tr=tb.UserData[IgnoredFieldsLabel]:>List.[string]; + // foreach(opt in options.IgnoreFields) + // tr.Add(opt); - def lst = tb.UserData[IgnoredFieldsLabel] :> List[string]; - unless (lst.Contains(field.Name)) - lst.Add(field.Name); - } - // adds a property to ignore list - public Ignore(tb : TypeBuilder, prop : ClassMember.Property) : void - { - unless (tb.UserData.Contains(IgnoredPropertiesLabel)) tb.UserData.Add(IgnoredPropertiesLabel, List.[string]()); + def relevant_fields = get_relevant_fields(tb); - def lst = tb.UserData[IgnoredPropertiesLabel] :> List[string]; - unless (lst.Contains(prop.Name)) lst.Add(prop.Name); - } + // true if strict type equality is needed, i. e. no subtypes are allowed; + def typecheck_needed = !tb.IsSealed && !tb.IsVariantOption && !tb.IsValueType && options.CheckTypes; + //assert2(false); - // represents macro options - [Record] - struct SEOptions - { - [Accessor] _ignoreFields : list[string]; - [Accessor] _checkTypes : bool; + def isHashCode=DefineHashCode(tb, relevant_fields, options.EmitDebugSources, options.WhenGenerateCashField); + DefineEquality(tb, relevant_fields, typecheck_needed, options.EmitDebugSources, isHashCode); - [Accessor] static _default : SEOptions = SEOptions([], true); + DefineOperators(tb); + DefineStructural(tb); + } - public static Parse(options : list[PExpr]) : SEOptions + // adds a field to ignore list + public Ignore(tb : TypeBuilder, field : string, ignory:bool, IgnoredLabel:string) : void { - mutable check_types = true; - mutable ignore_fields = []; + unless (tb.UserData.Contains(IgnoredLabel)) + tb.UserData.Add(IgnoredLabel, Hashtable.[string,bool]()); - foreach (opt in options) - { - | <[ CheckTypes = true ]> => check_types = true; - | <[ CheckTypes = false ]> => check_types = false; - | <[ Ignore = [..$flds] ]> - | <[ Ignore = $fld ]> with flds = [fld] => + //def lst = tb.UserData[IgnoredFieldsLabel] :> List[string]; + //unless (lst.Contains(field.Name)) + // lst.Add(field.Name); + (tb.UserData[IgnoredLabel]:>Hashtable.[string,bool]).Add(field, ignory); + } - // add field names as strings - ignore_fields += flds.MapFiltered(_ is PExpr.Ref, x => (x :> PExpr.Ref).name.Id) - | _ => Message.Error("Unknown options for StructuralEquality.") - } + // represents macro options +[Record] + struct SEOptions + { + public IgnoreFields : Hashtable[string, bool] { get } + public CheckTypes : bool { get } + public EmitDebugSources : bool { get } + public WhenGenerateCashField :int; + public AllIgnore:bool; - SEOptions(ignore_fields, check_types) + public static Default : SEOptions { get; default SEOptions(Hashtable(), true, true, 7, false) } + + public static Parse(options : list[PExpr]) : SEOptions + { + mutable check_types = true; + mutable ignore_fields = Hashtable(); + mutable emitDebugSources = true; + mutable allIgnore = false; + mutable whenGenerateCashField = 7; + // assert2(false) ; + + foreach (opt in options) + { + | <[ CheckTypes = $(_check_types:bool) ]> => check_types=_check_types; + | <[ Ignore = [..$flds] ]> + | <[ Ignore = $fld ]> with flds = [fld] => + + foreach(fl in flds) + when (fl is PExpr.Ref|| fl is PExpr.Member) + ignore_fields.Add(fl.ToString(),true); + + + | <[ NotIgnore = [..$flds] ]> + | <[ NotIgnore = $fld ]> with flds = [fld] => + //assert2(false) ; + foreach(fl in flds) + when (fl is PExpr.Ref|| fl is PExpr.Member) +// ignore_fields.Add((fl :> PExpr.Ref).name.Id,false); + ignore_fields.Add(fl.ToString(),false); + + + | <[ AllIgnore = true ]> => allIgnore = true; + + | <[ EmitDebugSources = $(_EmitDebugSources:bool) ]> => emitDebugSources = _EmitDebugSources; + | <[ WhenGenerateCashField = $(_WhenGenerateCashField:int) ]> => whenGenerateCashField = _WhenGenerateCashField; + + | _ => Message.Error("Unknown options for StructuralEquality.") + } + + SEOptions(ignore_fields, check_types, emitDebugSources, whenGenerateCashField, allIgnore) + } } - } - - GetEqualsImpl(typeInfoToLookup : TypeInfo, paramType : FixedType.Class) : option[IMethod] - { - def baseEqualsImpl = typeInfoToLookup - .LookupMember("EqualsImpl") - .NOfTypeRev.[_, IMethod]() - .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(paramType)); - - baseEqualsImpl - } - - IsParentImplement_EqualsImpl(tb : TypeBuilder) : bool - { - GetEqualsImpl(tb.BaseType, tb.BaseClass).IsSome - } - - HasTypedEquals(type : FixedType) : bool - { - | Class(ti, _) => + + GetEqualsImpl(typeInfoToLookup : TypeInfo, paramType : FixedType.Class) : option[IMethod] + { + def baseEqualsImpl = typeInfoToLookup + .LookupMember("EqualsImpl") + .MapFiltered(_ is IMethod, _ :> IMethod) //.Filter(_ is IMethod).Map(_ :> IMethod) // .NOfTypeRev.[_, IMethod]() + + .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(paramType)); + + baseEqualsImpl + } + + IsParentImplement_EqualsImpl(tb : TypeBuilder) : bool + { + GetEqualsImpl(tb.BaseType, tb.BaseClass).IsSome + } + + HasTypedEquals(type : FixedType) : bool + { + | Class(ti, _) => def result = ti - .LookupMember("Equals") - .NOfTypeRev.[_, IMethod]() - .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(type)); - + .LookupMember("Equals") + .MapFiltered(_ is IMethod, _ :>IMethod) + // .Filter(_ is IMethod).Map(_ :> IMethod) // .NOfTypeRev.[_, IMethod]() + .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(type)); + result.IsSome - - | _ => false - } - - DefineEquality(tb : TypeBuilder, fields : Seq[IField], _check_types : bool) : void - { - assert2(tb.BaseType.LookupMemberAvailable); - def defineMember(member : ClassMember) : void { _ = tb.DefineWithSource(member); } + | _ => false + } - // generates comparison code for a single field - def invokeEquals(x : IField) - { - def type = x.GetMemType(); - def value = <[ $(x.Name : usesite) ]>; - - if (type.IsPrimitive) + DefineEquality(tb : TypeBuilder, fields : Seq[IField], _check_types : bool, emitDebugSources : bool, isHashCode: bool) : void + { + //assert2(tb.BaseType.LookupMemberAvailable); + def defineMember(member : ClassMember) : void + { + if (emitDebugSources) _ = tb.DefineWithSource(member) + else tb.Define(member); + } + + // generates comparison code for a single field + def invokeEquals(x : IField) + { + + def type = x.GetMemType(); + def value = <[ $(x.Name : usesite) ]>; + + if (type.IsPrimitive) <[ this.$value == other.$value ]> // primitive magic - else if (type.Equals(tb.InternalType.String)) + else if (type.Equals(tb.InternalType.String)) <[ string.Equals(this.$value, other.$value) ]> - else if (type.IsValueType && !type.CanBeNull && HasTypedEquals(type)) + else if (type.IsValueType && !type.CanBeNull && HasTypedEquals(type)) <[ this.$value.Equals(other.$value) ]> // no null-checks // <[ if ($value.HasValue) other.$value.HasValue && this.$value.Value.Equals(other.$value.Value); else !other.$value.HasValue; ]> // For T? //else if (type is FixedType.StaticTypeVarRef) // for type parameters - // <[ EqualityComparer.Default.Equals($value, other.$value) ]> - else + // <[ EqualityComparer.Default.Equals($value, other.$value) ]> + else <[ EqualityComparer.Default.Equals(this.$value, other.$value) ]>; - } - - def type = GetTypeName(tb); - //def type_checker = - // if (check_types) - // <[ other.GetType().Equals(this.GetType()) ]> - // else - // <[ true ]>; - - def isParentImplement_EqualsImpl = IsParentImplement_EqualsImpl(tb); - def parentType = tb.BaseClass; - def baseCall = - if (isParentImplement_EqualsImpl) + } + + + def type = GetTypeName(tb); + //def type_checker = + // if (check_types) + // <[ other.GetType().Equals(this.GetType()) ]> + // else + // <[ true ]>; + + def isParentImplement_EqualsImpl = IsParentImplement_EqualsImpl(tb); + def parentType = tb.BaseClass; + def baseCall = + if (isParentImplement_EqualsImpl) <[ base.EqualsImpl(other : $(parentType : typed)) ]> - else + else <[ true ]>; - - // core comparison code (type checker + comparison for each field) - def body = fields.Fold(baseCall, (f, acc) => <[ $(invokeEquals(f)) && $acc ]> ); - - // no null-check for structs - def fun_body = if (tb.GetMemType().CanBeNull) <[ match (other) { | null => false | _ => $body } ]> else body; - def fun_body = <[ - _ = other; // shut the compiler up if body degrades to "true" + + // core comparison code (type checker + comparison for each field) + def body = fields.Fold(baseCall, (f, acc) => <[ $(invokeEquals(f)) && $acc ]> ); + def body = if(isHashCode) + { <[ if(this._hashCode!=other._hashCode) false else {$body} ]> + }else <[ $body ]>; + + // no null-check for structs + def fun_body = if (tb.GetMemType().CanBeNull) <[ match (other) { | null => false | _ => $body } ]> else body; + def fun_body = <[ +_ = other; // shut the compiler up if body degrades to "true" $fun_body ]>; - match (GetEqualsImpl(tb, tb.GetMemType())) - { - | Some(method is MethodBuilder) => - method.Body = fun_body; - //assert2(false); - method.Ast.Body = fun_body; - tb.TyManager.GenerateFakeSourceCode(method.Ast); - - | _ => Message.FatalError($"Can't find EqualsImpl(other : $(tb.GetMemType())) in $tb"); - } - - def implementsEquatable = AskUserData(tb, IsEquatableImplementedLabel); - if (implementsEquatable) - defineMember(<[ decl: + match (GetEqualsImpl(tb, tb.GetMemType())) + { + | Some(method is MethodBuilder) => + method.Body = fun_body; + //assert2(false); + method.Ast.Body = fun_body; + when (emitDebugSources) + tb.TyManager.GenerateFakeSourceCode(tb, method.Ast); + + | _ => Message.FatalError($"Can't find EqualsImpl(other : $(tb.GetMemType())) in $tb"); + } + + def implementsEquatable = AskUserData(tb, IsEquatableImplementedLabel); + if (implementsEquatable) + defineMember(<[ decl: public Equals(other : $type) : bool implements System.IEquatable.[$type].Equals { - EqualsImpl(other) + EqualsImpl(other) } ]>); - else - defineMember(<[ decl: + else + defineMember(<[ decl: public Equals(other : $type) : bool { - EqualsImpl(other) + EqualsImpl(other) } ]>); - - when (isParentImplement_EqualsImpl) - defineMember(<[ decl: - protected override EqualsImpl(other : $(parentType : typed)) : bool - { + + when (isParentImplement_EqualsImpl) + defineMember(<[ decl: + protected override EqualsImpl(other : $(parentType : typed)) : bool + { | x is $type => EqualsImpl(x) - | _ => false - } + | _ => false + } ]>); - - // implements object.Equals - defineMember(<[ decl: + + // implements object.Equals + defineMember(<[ decl: public override Equals(other : System.Object) : bool { - | x is $type => EqualsImpl(x) - | _ => false + | x is $type => EqualsImpl(x) + | _ => false + } + ]>); } - ]>); - } - // uses http://en.wikipedia.org/wiki/Jenkins_hash_function to implement GetHashCode - DefineHashCode(tb : TypeBuilder, fields : Seq[IField]) : void - { - def callBase = IsParentImplement_EqualsImpl(tb); - - def hash_body = fields.Map( - fun (f) + DefineHashCode(tb : TypeBuilder, fields : Seq[IField], emitDebugSources : bool, WhenGenerateCashField:int) : bool { - def type = f.GetMemType(); - def value = <[ $(f.Name : usesite) ]>; - def gethashcode = + def callBase = IsParentImplement_EqualsImpl(tb); + + def (countConst, hash_bodyConst, hash_bodyMutable) = fields.Fold((0,<[-2128831035]>,<[-2128831035]>), + fun ( f, (countConst, initExprConst, initExprMutable)) + { + + + def type = f.GetMemType(); + def value = <[ $(f.Name : usesite) ]>; + + + + def gethashcode = + + if (type.Equals(tb.InternalType.Int32)) - value + <[ $value ]> else if (type.Equals(tb.InternalType.UInt32)) - <[ unchecked ($value :> int) ]> - else if (type.IsValueType) - if (type.CanBeNull) - <[ EqualityComparer.Default.GetHashCode($value) ]> // можно оптимизировать - else - <[ this.$value.GetHashCode() ]> - else if (type.Equals(tb.InternalType.Int32)) - <[ this.$value ]> - else if (type is FixedType.StaticTypeVarRef) - <[ EqualityComparer.Default.GetHashCode($value) ]> + <[ $value :> int ]> + else if (type.Equals(tb.InternalType.Boolean)) + <[ if($value) 0x959595C3C3 else 0xC953C953 ]> + else + if (type.IsValueType) + if (type.CanBeNull) <[if($value is null) 0x359CC953 else $value.GetHashCode()]> else <[$value.GetHashCode()]> else - <[ this.$value?.GetHashCode() ]>; - - <[ - hash += $gethashcode; - hash += (hash << 10); - hash ^= (hash >> 6); - ]> - }); + if (type is FixedType.StaticTypeVarRef) <[ EqualityComparer.Default.GetHashCode($value) ]> else <[if($value is null) 0x359CC953 else $value.GetHashCode()]>; + + + + //else if (type.IsValueType) + // if (type.CanBeNull) + // <[ EqualityComparer.Default.GetHashCode($value) ]> // можно оптимизировать + // else + // <[ this.$value.GetHashCode() ]> + //else if (type is FixedType.StaticTypeVarRef) + // <[ EqualityComparer.Default.GetHashCode($value) ]> + //else + // <[ this.$value?.GetHashCode() ]>; + if(!f.IsMutable&(type.IsPrimitive||type.Equals(tb.InternalType.String))) - def variantOptionHash = - if (tb.IsVariantOption) { - def optionNumber = tb.GetVariantOptionParent().GetVariantOptions().IndexOf(tb) + 1; - assert(optionNumber > 0); - <[ hash ^= $(optionNumber : int); ]> - } - else - <[ () ]>; - - def baseHash = - if (callBase) - <[ hash ^= base.GetHashCode(); ]> + def tmp = <[ ($initExprConst) ^ ($gethashcode) ]>; + + ( + countConst+1, + // <[ (_*_) (0x01000193, (($initExprConst) ^ ($gethashcode)) )]>, +// def tmp = <[($initExprConst) ^ ($gethashcode)]> + <[ (0x01000193 * ($tmp) )]>, + <[$initExprMutable]> + + ) + } else - <[ () ]>; + { + def tmp= <[ ($initExprMutable) ^ ($gethashcode) ]>; + ( + countConst, + <[ $initExprConst]>, + // <[ (_*_) (0x01000193, (($initExprMutable) ^ ($gethashcode)) )]> + <[ (0x01000193 * ($tmp) )]> - def body = - if (hash_body.IsEmpty) - if (callBase) <[ base.GetHashCode() ]> else <[ 0 ]> - else - <[ - unchecked + ) + }; + + }); + + + + + def hash_bodyConst = + if (tb.IsVariantOption) + { + def optionNumber = tb.VariantOptionCode+1; + assert(optionNumber > 0); + <[ ($hash_bodyConst) + ($(optionNumber : int)) ]> + } + else + <[ $hash_bodyConst ]>; + + def hash_bodyMutable = if (callBase) <[ ($hash_bodyMutable) ^ (base.GetHashCode()) ]> else <[ $hash_bodyMutable ]>; + + def body_hashcode = + + <[ unchecked { - mutable hash : int; - { ..$hash_body } - //hash += (hash << 3); - //hash ^= (hash >> 11); - //hash += (hash << 15); - $baseHash; - $variantOptionHash; - hash + when(_hashCode==0) _hashCode= $hash_bodyConst; + $hash_bodyMutable ^ _hashCode; } - ]>; + ]>; - _ = tb.DefineWithSource(<[ decl: public override GetHashCode() : int { $body } ]>); - } + def body = - DefineOperators(tb : TypeBuilder) : void - { - def type = GetTypeName(tb); + <[ unchecked + { +// def _hashCode= $hash_bodyConst; + $hash_bodyMutable ^ $hash_bodyConst + } + ]>; + + + def define(expr) + { + if (emitDebugSources) _ = tb.DefineWithSource(expr) + else tb.Define(expr) + } + + + def isHashCode=countConst>=WhenGenerateCashField; + + if(isHashCode) + { + define(<[ decl: private mutable _hashCode : int; ]> ); + define(<[ decl: public override GetHashCode() : int { $body_hashcode } ]>); + }else + { + define(<[ decl: public override GetHashCode() : int { $body } ]>); + + } + + isHashCode + } + + DefineOperators(tb : TypeBuilder) : void + { + def type = GetTypeName(tb); - if (tb.IsValueType) - { - tb.Define(<[ decl: - public static @==(first : $type, second : $type) : bool - { + if (tb.IsValueType) + { + tb.Define(<[ decl: + public static @==(first : $type, second : $type) : bool + { first.Equals(second) - } + } ]>); - } - else - { - tb.Define(<[ decl: - public static @==(first : $type, second : $type) : bool - { + } + else + { + tb.Define(<[ decl: + public static @==(first : $type, second : $type) : bool + { if (first is null) second is null else first.Equals(second) - } + } ]>); - } + } - tb.Define(<[ decl: + tb.Define(<[ decl: public static @!= (first : $type, second : $type) : bool { - !(first == second) + !(first == second) + } + ]>); } - ]>); - } - DefineStructural(tb : TypeBuilder) : void - { - when (AskUserData(tb, IsStructuralEquatableImplementedLabel) == true) - { - tb.Define(<[ decl: - public Equals(other : object, _comparer : System.Collections.IEqualityComparer) : bool - { + DefineStructural(tb : TypeBuilder) : void + { + when (AskUserData(tb, IsStructuralEquatableImplementedLabel)) + unless (tb.BaseType?.IsDerivedFrom(tb.UserData[StructuralEquatableTypeInfoLabel] :> TypeInfo)) + { + tb.Define(<[ decl: + public Equals(other : object, _comparer : System.Collections.IEqualityComparer) : bool + { Equals(other); - } + } ]>); - tb.Define(<[ decl: - public GetHashCode(_comparer : System.Collections.IEqualityComparer) : int - { + tb.Define(<[ decl: + public GetHashCode(_comparer : System.Collections.IEqualityComparer) : int + { GetHashCode(); - } + } ]>); - } - } + } + + + } //MarkCompilerGenerated(cm : ClassMember) : ClassMember //{ - // cm.AddCustomAttribute(<[System.Runtime.CompilerServices.CompilerGenerated]>); - // cm + // cm.AddCustomAttribute(<[System.Runtime.CompilerServices.CompilerGenerated]>); + // cm //} // no api to get type name with params; 'this' keyword in this context is bugged - GetTypeName(tb : TypeBuilder) : PExpr - { - //<[ $(tb.GetMemType() : typed) ]> - def splicable_to_ref(s : Splicable) - { - | Name(n) - | HalfId(n) => PExpr.Ref(n) - | Expression(e) => e - } - - def qname = PExpr.FromQualifiedIdentifier(tb.Manager, tb.Ast.FullQualifiedName); - if (tb.Ast.TypeParameters is null) + GetTypeName(tb : TypeBuilder) : PExpr + { + //<[ $(tb.GetMemType() : typed) ]> + def splicable_to_ref(s : Splicable) + { + | Name(n) + | HalfId(n) => PExpr.Ref(n) + | Expression(e) => e + } + + def qname = PExpr.FromQualifiedIdentifier(tb.Manager, tb.Ast.FullQualifiedName); + if (tb.Ast.TypeParameters is null) <[ $qname ]> - else - { - def args = tb.Ast.TypeParameters.tyvars.Map(splicable_to_ref); + else + { + def args = tb.Ast.TypeParameters.tyvars.Map(splicable_to_ref); <[ $qname.[..$args] ]> - } - } + } + } - AskUserData(tb : TypeBuilder, question : string, defaultAnswer : bool = false) : bool - { - if (!tb.UserData.Contains(question)) defaultAnswer else tb.UserData[question] :> bool + AskUserData(tb : TypeBuilder, question : string, defaultAnswer : bool = false) : bool + { + if (!tb.UserData.Contains(question)) defaultAnswer else tb.UserData[question] :> bool + } } - } }