/**
 * Copyright (c) 2016-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import * as AlgebraicType from '../algebraic-type' ; import * as AlgebraicTypeUtils from '../algebraic-type-utils' ; import * as Code from '../code' ; import * as Error from '../error' ; import * as FileWriter from '../file-writer' ; import * as Maybe from '../maybe' ; import * as ObjC from '../objc' ; import * as ObjCTypeUtils from '../objc-type-utils' ; import * as ObjectSpec from '../object-spec' ; import * as ObjectSpecUtils from '../object-spec-utils' ; import * as ObjectSpecCodeUtils from '../object-spec-code-utils' ; enum EqualityFunctionType { compareFloats , compareDoubles , compareCGFloats , hashFloat , hashDouble , hashCGFloat } class EqualityFunction { private equalityFunctionType ; constructor ( type ) { this . equalityFunctionType = type ; } static CompareFloats ( ) { return new EqualityFunction ( EqualityFunctionType . compareFloats ) ; } static CompareDoubles ( ) { return new EqualityFunction ( EqualityFunctionType . compareDoubles ) ; } static CompareCGFloats ( ) { return new EqualityFunction ( EqualityFunctionType . compareCGFloats ) ; } static HashFloat ( ) { return new EqualityFunction ( EqualityFunctionType . hashFloat ) ; } static HashDouble ( ) { return new EqualityFunction ( EqualityFunctionType . hashDouble ) ; } static HashCGFloat ( ) { return new EqualityFunction ( EqualityFunctionType . hashCGFloat ) ; } match < T > ( compareFloats , compareDoubles , compareCGFloats , hashFloat , hashDouble , hashCGFloat ) { switch ( this . equalityFunctionType ) { case EqualityFunctionType . compareFloats : return compareFloats ( ) ; case EqualityFunctionType . compareDoubles : return compareDoubles ( ) ; case EqualityFunctionType . compareCGFloats : return compareCGFloats ( ) ; case EqualityFunctionType . hashFloat : return hashFloat ( ) ; case EqualityFunctionType . hashDouble : return hashDouble ( ) ; case EqualityFunctionType . hashCGFloat : return hashCGFloat ( ) ; } return null ! ; } } function nameOfEqualityFunction ( equalityFunction ) { return equalityFunction . match ( function ( ) : string { return 'CompareFloats' ; } , function ( ) : string { return 'CompareDoubles' ; } , function ( ) : string { return 'CompareCGFloats' ; } , function ( ) : string { return 'HashFloat' ; } , function ( ) : string { return 'HashDouble' ; } , function ( ) : string { return 'HashCGFloat' ; } ) ; } function equalityFunctionsToIncludeForEqualityFunction ( equalityFunction ) { return equalityFunction . match ( function ( ) : EqualityFunction [ ] { return [ EqualityFunction . CompareFloats ( ) ] ; } , function ( ) : EqualityFunction [ ] { return [ EqualityFunction . CompareDoubles ( ) ] ; } , function ( ) : EqualityFunction [ ] { return [ EqualityFunction . CompareFloats ( ) , EqualityFunction . CompareDoubles ( ) , EqualityFunction . CompareCGFloats ( ) , ] ; } , function ( ) : EqualityFunction [ ] { return [ EqualityFunction . HashFloat ( ) ] ; } , function ( ) : EqualityFunction [ ] { return [ EqualityFunction . HashDouble ( ) ] ; } , function ( ) : EqualityFunction [ ] { return [ EqualityFunction . HashFloat ( ) , EqualityFunction . HashDouble ( ) , EqualityFunction . HashCGFloat ( ) , ] ; } ) ; } enum ComputationCostType { ivarAccess , pointerComparison , functionInvocation , objectMessageSend } class ComputationCost { private computationCostType ; constructor ( type ) { this . computationCostType = type ; } static ivarAccess ( ) { return new ComputationCost ( ComputationCostType . ivarAccess ) ; } static PointerComparison ( ) { return new ComputationCost ( ComputationCostType . pointerComparison ) ; } static FunctionInvocation ( ) { return new ComputationCost ( ComputationCostType . functionInvocation ) ; } static ObjectMessageSend ( ) { return new ComputationCost ( ComputationCostType . objectMessageSend ) ; } match < T > ( ivarAccess , pointerComparison , functionInvocation , objectMessageSend ) { switch ( this . computationCostType ) { case ComputationCostType . ivarAccess : return ivarAccess ( ) ; case ComputationCostType . pointerComparison : return pointerComparison ( ) ; case ComputationCostType . functionInvocation : return functionInvocation ( ) ; case ComputationCostType . objectMessageSend : return objectMessageSend ( ) ; } return null ! ; } } function computationCostAsNumber ( computationCost ) { return computationCost . match ( function ( ) : number { return 1 ; } , function ( ) : number { return 2 ; } , function ( ) : number { return 3 ; } , function ( ) : number { return 4 ; } ) ; } interface SystemFunction { name : string ; import : ObjC . Import | null ; } interface TypeEqualityValue { value : string ; functionsToInclude : EqualityFunction [ ] ; importsToInclude : ObjC . Import [ ] ; computationCost : ComputationCost ; } interface TypeEqualityGenerationGroup { equalityCheckGenerator : ( attributeValueAccessor : string ) => TypeEqualityValue [ ] ; hashGenerator : ( attributeValueAccessor : string ) => TypeEqualityValue [ ] ; } function attributeValueOnComparisonObject ( attributeValueAccessor ) { return 'object->' + attributeValueAccessor ; } const GENERATOR_FOR_COMPARING_POINTER_EQUALITY = function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : attributeValueAccessor + ' == ' + attributeValueOnComparisonObject ( attributeValueAccessor ) , functionsToInclude : [ ] , importsToInclude : [ ] , computationCost : ComputationCost . PointerComparison ( ) } , ] ; } ; function stringForInvokingObjectEqualityOfAttributeValue ( attributeValueAccessor ) { return ( '[' + attributeValueAccessor + ' isEqual:' + attributeValueOnComparisonObject ( attributeValueAccessor ) + ']' ) ; } function selectValue ( typeEqualityValue ) { return typeEqualityValue . value ; } function buildTypeEqualityValueFunctionsToInclude ( soFar , typeEqualityValue ) { return soFar . concat ( typeEqualityValue . functionsToInclude ) ; } function buildTypeEqualityValueImportsToInclude ( soFar , typeEqualityValue ) { return soFar . concat ( typeEqualityValue . importsToInclude ) ; } const GENERATOR_FOR_COMPARING_OBJECT_EQUALITY = function ( attributeValueAccessor ) : TypeEqualityValue [ ] { const pointerEqualityValues = GENERATOR_FOR_COMPARING_POINTER_EQUALITY ( attributeValueAccessor ) ; return [ { value : '(' + pointerEqualityValues . map ( selectValue ) . join ( ' && ' ) + ' ? YES : ' + stringForInvokingObjectEqualityOfAttributeValue ( attributeValueAccessor ) + ')' , functionsToInclude : pointerEqualityValues . reduce ( buildTypeEqualityValueFunctionsToInclude , [ ] ) , importsToInclude : pointerEqualityValues . reduce ( buildTypeEqualityValueImportsToInclude , [ ] ) , computationCost : ComputationCost . ObjectMessageSend ( ) } , ] ; } ; const GENERATOR_FOR_HASHING_POINTER_VALUE = function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : attributeValueAccessor , functionsToInclude : [ ] , importsToInclude : [ ] , computationCost : ComputationCost . ivarAccess ( ) } , ] ; } ; const GENERATOR_FOR_HASHING_OBJECT_VALUE = function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : '[' + attributeValueAccessor + ' hash]' , functionsToInclude : [ ] , importsToInclude : [ ] , computationCost : ComputationCost . ObjectMessageSend ( ) } , ] ; } ; function returnEmptyArray ( ) { return [ ] ; } function generatorForCastingAttributeValueToType ( targetType ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : '(' + targetType . reference + ')' + attributeValueAccessor , functionsToInclude : [ ] , importsToInclude : [ ] , computationCost : ComputationCost . ivarAccess ( ) } , ] ; } ; } function generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( systemFunction ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : systemFunction . name + '(' + attributeValueAccessor + ', ' + attributeValueOnComparisonObject ( attributeValueAccessor ) + ')' , functionsToInclude : [ ] , importsToInclude : Maybe . match ( function ( importToInclude ) : ObjC . Import [ ] { return [ importToInclude ] ; } , returnEmptyArray , systemFunction . import ) , computationCost : ComputationCost . FunctionInvocation ( ) } , ] ; } ; } function generatorForInvokingFunctionWithBothAttributeValues ( equalityFunction ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : nameOfEqualityFunction ( equalityFunction ) + '(' + attributeValueAccessor + ', ' + attributeValueOnComparisonObject ( attributeValueAccessor ) + ')' , functionsToInclude : equalityFunctionsToIncludeForEqualityFunction ( equalityFunction ) , importsToInclude : [ ] , computationCost : ComputationCost . FunctionInvocation ( ) } , ] ; } ; } function generatorForInvokingSystemFunctionWithNameWithAttributeValue ( systemFunction ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : systemFunction . name + '(' + attributeValueAccessor + ')' , functionsToInclude : [ ] , importsToInclude : Maybe . match ( function ( importToInclude ) : ObjC . Import [ ] { return [ importToInclude ] ; } , returnEmptyArray , systemFunction . import ) , computationCost : ComputationCost . FunctionInvocation ( ) } , ] ; } ; } function generatorForInvokingFunctionWithAttributeValue ( equalityFunction ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return [ { value : nameOfEqualityFunction ( equalityFunction ) + '(' + attributeValueAccessor + ')' , functionsToInclude : equalityFunctionsToIncludeForEqualityFunction ( equalityFunction ) , importsToInclude : [ ] , computationCost : ComputationCost . FunctionInvocation ( ) } , ] ; } ; } function buildTypeEqualityValuesFromGenerators ( attributeValueAccessor , soFar , generator ) { return soFar . concat ( generator ( attributeValueAccessor ) ) ; } function generatorForInvokingSubGenerators ( generators ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { return generators . reduce ( ( soFar , generator ) => buildTypeEqualityValuesFromGenerators ( attributeValueAccessor , soFar , generator ) , [ ] ) ; } ; } const NSOBJECT_GENERATION_GROUP = { equalityCheckGenerator : GENERATOR_FOR_COMPARING_OBJECT_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_OBJECT_VALUE } ; const NSUINTEGER_TYPE = { name : 'NSUInteger' , reference : 'NSUInteger' } ; const DOUBLE_TYPE = { name : 'double' , reference : 'double' } ; const CGFLOAT_TYPE = { name : 'CGFloat' , reference : 'CGFloat' } ; const CGPOINT_TYPE = { name : 'CGPoint' , reference : 'CGPoint' } ; const CGSIZE_TYPE = { name : 'CGSize' , reference : 'CGSize' } ; function generationGroupForType ( type ) { return ObjCTypeUtils . matchType ( { id : function ( ) { return NSOBJECT_GENERATION_GROUP ; } , NSObject : function ( ) { return NSOBJECT_GENERATION_GROUP ; } , BOOL : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : generatorForCastingAttributeValueToType ( NSUINTEGER_TYPE ) } ; } , NSInteger : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : generatorForInvokingSystemFunctionWithNameWithAttributeValue ( { name : 'ABS' , import : null } ) } ; } , NSUInteger : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_POINTER_VALUE } ; } , double : function ( ) { return { equalityCheckGenerator : generatorForInvokingFunctionWithBothAttributeValues ( EqualityFunction . CompareDoubles ( ) ) , hashGenerator : generatorForInvokingFunctionWithAttributeValue ( EqualityFunction . HashDouble ( ) ) } ; } , float : function ( ) { return { equalityCheckGenerator : generatorForInvokingFunctionWithBothAttributeValues ( EqualityFunction . CompareFloats ( ) ) , hashGenerator : generatorForInvokingFunctionWithAttributeValue ( EqualityFunction . HashFloat ( ) ) } ; } , CGFloat : function ( ) { return { equalityCheckGenerator : generatorForInvokingFunctionWithBothAttributeValues ( EqualityFunction . CompareCGFloats ( ) ) , hashGenerator : generatorForInvokingFunctionWithAttributeValue ( EqualityFunction . HashCGFloat ( ) ) } ; } , NSTimeInterval : function ( ) { return { equalityCheckGenerator : generatorForProvidingEqualityValuesFromGeneratorOfType ( DOUBLE_TYPE ) , hashGenerator : generatorForProvidingHashValuesFromGeneratorOfType ( DOUBLE_TYPE ) } ; } , uintptr_t : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_POINTER_VALUE } ; } , uint32_t : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_POINTER_VALUE } ; } , uint64_t : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_POINTER_VALUE } ; } , int32_t : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : GENERATOR_FOR_HASHING_POINTER_VALUE } ; } , int64_t : function ( ) { return { equalityCheckGenerator : GENERATOR_FOR_COMPARING_POINTER_EQUALITY , hashGenerator : generatorForInvokingSystemFunctionWithNameWithAttributeValue ( { name : 'ABS' , import : null } ) } ; } , SEL : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'sel_isEqual' , import : { library : 'objc' , file : 'runtime.h' , isPublic : false , requiresCPlusPlus : false } } ) , hashGenerator : ( attributeValueAccessor ) => [ { value : `NSStringFromSelector(${ attributeValueAccessor }).hash` , functionsToInclude : [ ] , importsToInclude : [ ] , computationCost : ComputationCost . ObjectMessageSend ( ) } , ] } ; } , NSRange : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'NSEqualRanges' , import : null } ) , hashGenerator : generatorForInvokingSubGenerators ( [ generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( NSUINTEGER_TYPE , 'location' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( NSUINTEGER_TYPE , 'length' ) , ] ) } ; } , CGRect : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'CGRectEqualToRect' , import : null } ) , hashGenerator : generatorForInvokingSubGenerators ( [ generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGPOINT_TYPE , 'origin' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGSIZE_TYPE , 'size' ) , ] ) } ; } , CGPoint : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'CGPointEqualToPoint' , import : null } ) , hashGenerator : generatorForInvokingSubGenerators ( [ generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'x' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'y' ) , ] ) } ; } , CGSize : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'CGSizeEqualToSize' , import : null } ) , hashGenerator : generatorForInvokingSubGenerators ( [ generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'width' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'height' ) , ] ) } ; } , UIEdgeInsets : function ( ) { return { equalityCheckGenerator : generatorForInvokingSystemFunctionWithNameWithBothAttributeValues ( { name : 'UIEdgeInsetsEqualToEdgeInsets' , import : null } ) , hashGenerator : generatorForInvokingSubGenerators ( [ generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'top' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'left' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'bottom' ) , generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( CGFLOAT_TYPE , 'right' ) , ] ) } ; } , Class : function ( ) { return NSOBJECT_GENERATION_GROUP ; } , dispatch_block_t : function ( ) { return NSOBJECT_GENERATION_GROUP ; } , unmatchedType : function ( ) { return null ! ; } } , type ) ; } function generatorForProvidingHashValuesFromGeneratorOfTypeWithSubValue ( type , propertyName ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { const generationGroup = generationGroupForType ( type ) ; return generationGroup . hashGenerator ( attributeValueAccessor + '.' + propertyName ) ; } ; } function generatorForProvidingEqualityValuesFromGeneratorOfType ( type ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { const generationGroup = generationGroupForType ( type ) ; return generationGroup . equalityCheckGenerator ( attributeValueAccessor ) ; } ; } function generatorForProvidingHashValuesFromGeneratorOfType ( type ) { return function ( attributeValueAccessor ) : TypeEqualityValue [ ] { const generationGroup = generationGroupForType ( type ) ; return generationGroup . hashGenerator ( attributeValueAccessor ) ; } ; } interface GeneratedTypeEqualityInformation { equalityChecks : TypeEqualityValue [ ] ; hashValues : TypeEqualityValue [ ] ; } function generatedTypeEqualityInformationForAttribute ( attribute ) { const type = ObjectSpecCodeUtils . computeTypeOfAttribute ( attribute ) ; const generationGroup = generationGroupForType ( type ) ; const attributeValueAccessor = ObjectSpecCodeUtils . ivarForAttribute ( attribute ) ; return generatedTypeEqualityInformationForGenerationGroup ( generationGroup , attributeValueAccessor ) ; } function buildImportsToInclude ( soFar , generatedTypeEqualityInformation ) { return soFar . concat ( generatedTypeEqualityInformation . equalityChecks . reduce ( buildTypeEqualityValueImportsToInclude , [ ] ) ) . concat ( generatedTypeEqualityInformation . hashValues . reduce ( buildTypeEqualityValueImportsToInclude , [ ] ) ) ; } function buildEqualityChecks ( soFar , generatedTypeEqualityInformation ) { return soFar . concat ( generatedTypeEqualityInformation . equalityChecks ) ; } function stringForIncludingEqualityCheckInCode ( equalityCheck ) { return '  ' + equalityCheck + ' &&' ; } enum ComparisonResult { OrderedAscending = - 1 , OrderedSame = 0 , OrderedDescending = 1 } function compareTypeEqualityValuesByComputationCost ( typeEqualityValue , typeEqualityValueToCompare ) { const baseComputationCostValue = computationCostAsNumber ( typeEqualityValue . computationCost ) ; const comparisonComputationCostValue = computationCostAsNumber ( typeEqualityValueToCompare . computationCost ) ; if ( baseComputationCostValue < comparisonComputationCostValue ) { return ComparisonResult . OrderedAscending ; } else if ( baseComputationCostValue > comparisonComputationCostValue ) { return ComparisonResult . OrderedDescending ; } else { return ComparisonResult . OrderedSame ; } } function isEqualInstanceMethod ( typeName , generatedTypeEqualityInformation ) { const openingCode = [ 'if (self == object) {' , '  return YES;' , '} else if (object == nil || ![object isKindOfClass:[self class]]) {' , '  return NO;' , '}' , 'return' , ] ; const equalityCheckEqualityValues = generatedTypeEqualityInformation . reduce ( buildEqualityChecks , [ ] ) ; const equalityCheckEqualityValuesSortedByCost = equalityCheckEqualityValues . concat ( ) . sort ( compareTypeEqualityValuesByComputationCost ) ; const equalityChecks = equalityCheckEqualityValuesSortedByCost . map ( selectValue ) ; const equalityChecksUpUntilLastOne = equalityChecks . slice ( 0 , equalityChecks . length - 1 ) . map ( stringForIncludingEqualityCheckInCode ) ; const lastEqualityCheck = equalityChecks [ equalityChecks . length - 1 ] ; const code = openingCode . concat ( equalityChecksUpUntilLastOne ) . concat ( '  ' + lastEqualityCheck + ';' ) ; return { preprocessors : [ ] , belongsToProtocol : 'NSObject' , keywords : [ { name : 'isEqual' , argument : { name : 'object' , modifiers : [ ] , type : { name : typeName , reference : ObjectSpecUtils . typeReferenceForValueTypeWithName ( typeName ) } } } , ] , code : code , comments : [ ] , compilerAttributes : [ ] , returnType : { type : { name : 'BOOL' , reference : 'BOOL' } , modifiers : [ ] } } ; } function buildHashValues ( soFar , generatedTypeEqualityInformation ) { return soFar . concat ( generatedTypeEqualityInformation . hashValues ) ; } function hashInstanceMethod ( generatedTypeEqualityInformation ) { const hashEqualityValues = generatedTypeEqualityInformation . reduce ( buildHashValues , [ ] ) ; const hashValues = hashEqualityValues . map ( selectValue ) ; const hashValuesGroup = hashValues . join ( ', ' ) ; return { preprocessors : [ ] , belongsToProtocol : 'NSObject' , keywords : [ { name : 'hash' , argument : null } , ] , code : [ 'NSUInteger subhashes[] = {' + hashValuesGroup + '};' , 'NSUInteger result = subhashes[0];' , 'for (int ii = 1; ii < ' + hashValues . length + '; ++ii) {' , '  unsigned long long base = (((unsigned long long)result) << 32 | subhashes[ii]);' , '  base = (~base) + (base << 18);' , '  base ^= (base >> 31);' , '  base *=  21;' , '  base ^= (base >> 11);' , '  base += (base << 6);' , '  base ^= (base >> 22);' , '  result = base;' , '}' , 'return result;' , ] , comments : [ ] , compilerAttributes : [ ] , returnType : { type : { name : 'NSUInteger' , reference : 'NSUInteger' } , modifiers : [ ] } } ; } const COMPARE_FLOATS_FN = { comments : [ ] , name : 'CompareFloats' , parameters : [ { type : { name : 'float' , reference : 'float' } , name : 'givenFloat' } , { type : { name : 'float' , reference : 'float' } , name : 'floatToCompare' } , ] , returnType : { type : { name : 'BOOL' , reference : 'BOOL' } , modifiers : [ ] } , code : [ 'return fabsf(givenFloat - floatToCompare) < FLT_EPSILON * fabsf(givenFloat + floatToCompare) || fabsf(givenFloat - floatToCompare) < FLT_MIN;' , ] , isPublic : false , isInline : false , compilerAttributes : [ ] } ; const HASH_FLOAT_FN = { comments : [ ] , name : 'HashFloat' , parameters : [ { type : { name : 'float' , reference : 'float' } , name : 'givenFloat' } , ] , returnType : { type : { name : 'NSUInteger' , reference : 'NSUInteger' } , modifiers : [ ] } , code : [ 'union {' , '  float key;' , '  uint32_t bits;' , '} u;' , 'u.key = givenFloat;' , 'NSUInteger h = (NSUInteger)u.bits;' , '#if !TARGET_RT_64_BIT' , 'h = ~h + (h << 15);' , 'h ^= (h >> 12);' , 'h += (h << 2);' , 'h ^= (h >> 4);' , 'h *= 2057;' , 'h ^= (h >> 16);' , '#else' , 'h += ~h + (h << 21);' , 'h ^= (h >> 24);' , 'h = (h + (h << 3)) + (h << 8);' , 'h ^= (h >> 14);' , 'h = (h + (h << 2)) + (h << 4);' , 'h ^= (h >> 28);' , 'h += (h << 31);' , '#endif' , 'return h;' , ] , isPublic : false , isInline : false , compilerAttributes : [ ] } ; const COMPARE_DOUBLES_FN = { comments : [ ] , name : 'CompareDoubles' , parameters : [ { type : { name : 'double' , reference : 'double' } , name : 'givenDouble' } , { type : { name : 'double' , reference : 'double' } , name : 'doubleToCompare' } , ] , returnType : { type : { name : 'BOOL' , reference : 'BOOL' } , modifiers : [ ] } , code : [ 'return fabs(givenDouble - doubleToCompare) < DBL_EPSILON * fabs(givenDouble + doubleToCompare) || fabs(givenDouble - doubleToCompare) < DBL_MIN;' , ] , isPublic : false , isInline : false , compilerAttributes : [ ] } ; const HASH_DOUBLE_FN = { comments : [ ] , name : 'HashDouble' , parameters : [ { type : { name : 'double' , reference : 'double' } , name : 'givenDouble' } , ] , returnType : { type : { name : 'NSUInteger' , reference : 'NSUInteger' } , modifiers : [ ] } , code : [ 'union {' , '  double key;' , '  uint64_t bits;' , '} u;' , 'u.key = givenDouble;' , 'NSUInteger p = u.bits;' , 'p = (~p) + (p << 18);' , 'p ^= (p >> 31);' , 'p *=  21;' , 'p ^= (p >> 11);' , 'p += (p << 6);' , 'p ^= (p >> 22);' , 'return (NSUInteger) p;' , ] , isPublic : false , isInline : false , compilerAttributes : [ ] } ; function wrapCGFloatTypeComparisonCodeToAvoidUnusedFunctionWarning ( doubleCode , floatCode ) { return [ '#if CGFLOAT_IS_DOUBLE' , '  BOOL useDouble = YES;' , '#else' , '  BOOL useDouble = NO;' , '#endif' , '  if (useDouble) {' , '    ' + doubleCode , '  } else {' , '    ' + floatCode , '  }' , ] ; } const COMPARE_CGFLOATS_FN = { comments : [ ] , name : 'CompareCGFloats' , parameters : [ { type : { name : 'CGFloat' , reference : 'CGFloat' } , name : 'givenCGFloat' } , { type : { name : 'CGFloat' , reference : 'CGFloat' } , name : 'cgFloatToCompare' } , ] , returnType : { type : { name : 'BOOL' , reference : 'BOOL' } , modifiers : [ ] } , code : wrapCGFloatTypeComparisonCodeToAvoidUnusedFunctionWarning ( 'return CompareDoubles(givenCGFloat, cgFloatToCompare);' , 'return CompareFloats(givenCGFloat, cgFloatToCompare);' ) , isPublic : false , isInline : false , compilerAttributes : [ ] } ; const HASH_CGFLOATS_FN = { comments : [ ] , name : 'HashCGFloat' , parameters : [ { type : { name : 'CGFloat' , reference : 'CGFloat' } , name : 'givenCGFloat' } , ] , returnType : { type : { name : 'NSUInteger' , reference : 'NSUInteger' } , modifiers : [ ] } , code : wrapCGFloatTypeComparisonCodeToAvoidUnusedFunctionWarning ( 'return HashDouble(givenCGFloat);' , 'return HashFloat(givenCGFloat);' ) , isPublic : false , isInline : false , compilerAttributes : [ ] } ; function functionDefinitionForEqualityFunction ( equalityFunction ) { return equalityFunction . match ( function ( ) : ObjC . Function { return COMPARE_FLOATS_FN ; } , function ( ) : ObjC . Function { return COMPARE_DOUBLES_FN ; } , function ( ) : ObjC . Function { return COMPARE_CGFLOATS_FN ; } , function ( ) : ObjC . Function { return HASH_FLOAT_FN ; } , function ( ) : ObjC . Function { return HASH_DOUBLE_FN ; } , function ( ) : ObjC . Function { return HASH_CGFLOATS_FN ; } ) ; } interface EqualityFunctionsToIncludeTracker { equalityFunctionsToInclude : EqualityFunction [ ] ; equalityFunctionsIncluded : { [ equalityFunctionName ] : boolean ; } ; } function objectIncludingValue ( existingObject , key , value ) { const updatedObject = { } ; for ( const existingKey in existingObject ) { if ( existingObject . hasOwnProperty ( existingKey ) ) { updatedObject [ existingKey ] = existingObject [ existingKey ] ; } } updatedObject [ key ] = value ; return updatedObject ; } function buildEqualityFunctionsToIncludeTracker ( tracker , equalityFunction ) { const equalityFunctionName = nameOfEqualityFunction ( equalityFunction ) ; if ( tracker . equalityFunctionsIncluded [ equalityFunctionName ] !== true ) { return { equalityFunctionsToInclude : tracker . equalityFunctionsToInclude . concat ( equalityFunction ) , equalityFunctionsIncluded : objectIncludingValue ( tracker . equalityFunctionsIncluded , equalityFunctionName , true ) } ; } else { return tracker ; } } function buildFunctionsToInclude ( soFar , generatedTypeEqualityInformation ) { return soFar . concat ( generatedTypeEqualityInformation . equalityChecks . reduce ( buildTypeEqualityValueFunctionsToInclude , [ ] ) ) . concat ( generatedTypeEqualityInformation . hashValues . reduce ( buildTypeEqualityValueFunctionsToInclude , [ ] ) ) ; } function functionsToIncludeForGeneratedTypeEqualityInformation ( generatedTypeEqualityInformation ) { const allEqualityFunctions = generatedTypeEqualityInformation . reduce ( buildFunctionsToInclude , [ ] ) ; const emptyTracker = { equalityFunctionsToInclude : [ ] , equalityFunctionsIncluded : { } } ; const tracker = allEqualityFunctions . reduce ( buildEqualityFunctionsToIncludeTracker , emptyTracker ) ; return tracker . equalityFunctionsToInclude . map ( functionDefinitionForEqualityFunction ) ; } function doesValueAttributeContainAnUnknownType ( attribute ) { const type = ObjectSpecCodeUtils . computeTypeOfAttribute ( attribute ) ; const generationGroup = generationGroupForType ( type ) ; return generationGroup == null ; } function attributeToUnknownTypeError ( objectType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Equality plugin does not know how to compare or hash the backing type "' + underlyingType + '" from ' + objectType . typeName + '.' + attribute . name + '. Did you declare the wrong backing type?' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Equality plugin does not know how to compare or hash the type "' + attribute . type . name + '" from ' + objectType . typeName + '.' + attribute . name + '. Did you forget to declare a backing type?' ) ; } , attribute . type . underlyingType ) ; } export function createPlugin ( ) { return { additionalFiles : function ( objectType ) : Code . File [ ] { return [ ] ; } , transformBaseFile : function ( objectType , baseFile ) : Code . File { return baseFile ; } , additionalTypes : function ( objectType ) : ObjectSpec . Type [ ] { return [ ] ; } , attributes : function ( objectType ) : ObjectSpec . Attribute [ ] { return [ ] ; } , classMethods : function ( objectType ) : ObjC . Method [ ] { return [ ] ; } , transformFileRequest : function ( request ) : FileWriter . Request { return request ; } , fileType : function ( objectType ) : Code . FileType | null { return null ; } , forwardDeclarations : function ( objectType ) : ObjC . ForwardDeclaration [ ] { return [ ] ; } , functions : function ( objectType ) : ObjC . Function [ ] { if ( objectType . attributes . length > 0 ) { const generatedTypeEqualityInformation = objectType . attributes . map ( generatedTypeEqualityInformationForAttribute ) ; return functionsToIncludeForGeneratedTypeEqualityInformation ( generatedTypeEqualityInformation ) ; } else { return [ ] ; } } , headerComments : function ( objectType ) : ObjC . Comment [ ] { return [ ] ; } , imports : function ( objectType ) : ObjC . Import [ ] { if ( objectType . attributes . length > 0 ) { const generatedTypeEqualityInformation = objectType . attributes . map ( generatedTypeEqualityInformationForAttribute ) ; return generatedTypeEqualityInformation . reduce ( buildImportsToInclude , [ ] ) ; } else { return [ ] ; } } , implementedProtocols : function ( objectType ) : ObjC . ImplementedProtocol [ ] { return [ ] ; } , instanceMethods : function ( objectType ) : ObjC . Method [ ] { if ( objectType . attributes . length == 0 ) { return [ ] ; } const generatedTypeEqualityInformation = objectType . attributes . map ( generatedTypeEqualityInformationForAttribute ) ; return [ isEqualInstanceMethod ( objectType . typeName , generatedTypeEqualityInformation ) , hashInstanceMethod ( generatedTypeEqualityInformation ) , ] ; } , macros : function ( valueType ) : ObjC . Macro [ ] { return [ ] ; } , properties : function ( objectType ) : ObjC . Property [ ] { return [ ] ; } , requiredIncludesToRun : [ 'RMEquality' ] , staticConstants : function ( objectType ) : ObjC . Constant [ ] { return [ ] ; } , validationErrors : function ( objectType ) : Error . Error [ ] { return objectType . attributes . filter ( doesValueAttributeContainAnUnknownType ) . map ( ( attribute ) => attributeToUnknownTypeError ( objectType , attribute ) ) ; } , nullability : function ( objectType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( objectType ) : boolean { return false ; } } ; } function generatedTypeEqualityInformationForGenerationGroup ( generationGroup , valueAccessor ) { return { equalityChecks : generationGroup . equalityCheckGenerator ( valueAccessor ) , hashValues : generationGroup . hashGenerator ( valueAccessor ) } ; } function generatedTypeEqualityInformationForSubtypeAttributeForAlgebraicType ( algebraicType ) { const type = { name : 'NSUInteger' , reference : 'NSUInteger' } ; const generationGroup = generationGroupForType ( type ) ; return generatedTypeEqualityInformationForGenerationGroup ( generationGroup , AlgebraicTypeUtils . valueAccessorForInstanceVariableStoringSubtype ( ) ) ; } function generatedTypeEqualityInformationForAlgebraicSubtypeAttribute ( subtype , attribute ) { const type = AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) ; const generationGroup = generationGroupForType ( type ) ; return generatedTypeEqualityInformationForGenerationGroup ( generationGroup , AlgebraicTypeUtils . valueAccessorForInstanceVariableForAttribute ( subtype , attribute ) ) ; } function generatedTypeEqualityInformationForAlgebraicType ( algebraicType ) { const attributeGeneratedTypeEqualityInformation = AlgebraicTypeUtils . mapAttributesWithSubtypeFromSubtypes ( algebraicType . subtypes , generatedTypeEqualityInformationForAlgebraicSubtypeAttribute ) ; return [ generatedTypeEqualityInformationForSubtypeAttributeForAlgebraicType ( algebraicType ) , ] . concat ( attributeGeneratedTypeEqualityInformation ) ; } function doesAlgebraicAttributeContainAnUnknownType ( attribute ) { const type = AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) ; const generationGroup = generationGroupForType ( type ) ; return generationGroup == null ; } function algebraicAttributeToUnknownTypeError ( algebraicType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Equality plugin does not know how to compare or hash the backing type "' + underlyingType + '" from ' + algebraicType . name + '.' + attribute . name + '. Did you declare the wrong backing type?' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Equality plugin does not know how to compare or hash the type "' + attribute . type . name + '" from ' + algebraicType . name + '.' + attribute . name + '. Did you forget to declare a backing type?' ) ; } , attribute . type . underlyingType ) ; } export function createAlgebraicTypePlugin ( ) { return { additionalFiles : function ( algebraicType ) : Code . File [ ] { return [ ] ; } , transformBaseFile : function ( algebraicType , baseFile ) : Code . File { return baseFile ; } , blockTypes : function ( algebraicType ) : ObjC . BlockType [ ] { return [ ] ; } , classMethods : function ( algebraicType ) : ObjC . Method [ ] { return [ ] ; } , enumerations : function ( algebraicType ) : ObjC . Enumeration [ ] { return [ ] ; } , transformFileRequest : function ( request ) : FileWriter . Request { return request ; } , fileType : function ( algebraicType ) : Code . FileType | null { return null ; } , forwardDeclarations : function ( algebraicType ) : ObjC . ForwardDeclaration [ ] { return [ ] ; } , functions : function ( algebraicType ) : ObjC . Function [ ] { const generatedTypeEqualityInformation = generatedTypeEqualityInformationForAlgebraicType ( algebraicType ) ; return functionsToIncludeForGeneratedTypeEqualityInformation ( generatedTypeEqualityInformation ) ; } , headerComments : function ( algebraicType ) : ObjC . Comment [ ] { return [ ] ; } , implementedProtocols : function ( algebraicType ) : ObjC . ImplementedProtocol [ ] { return [ ] ; } , imports : function ( algebraicType ) : ObjC . Import [ ] { const generatedTypeEqualityInformation = generatedTypeEqualityInformationForAlgebraicType ( algebraicType ) ; return generatedTypeEqualityInformation . reduce ( buildImportsToInclude , [ ] ) ; } , instanceMethods : function ( algebraicType ) : ObjC . Method [ ] { const generatedTypeEqualityInformation = generatedTypeEqualityInformationForAlgebraicType ( algebraicType ) ; return [ isEqualInstanceMethod ( algebraicType . name , generatedTypeEqualityInformation ) , hashInstanceMethod ( generatedTypeEqualityInformation ) , ] ; } , instanceVariables : function ( algebraicType ) : ObjC . InstanceVariable [ ] { return [ ] ; } , macros : function ( algebraicType ) : ObjC . Macro [ ] { return [ ] ; } , requiredIncludesToRun : [ 'RMEquality' ] , staticConstants : function ( algebraicType ) : ObjC . Constant [ ] { return [ ] ; } , validationErrors : function ( algebraicType ) : Error . Error [ ] { return AlgebraicTypeUtils . allAttributesFromSubtypes ( algebraicType . subtypes ) . filter ( doesAlgebraicAttributeContainAnUnknownType ) . map ( ( attribute ) => algebraicAttributeToUnknownTypeError ( algebraicType , attribute ) ) ; } , nullability : function ( algebraicType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( algebraicType ) : boolean { return false ; } } ; }