@@ -457,6 +457,188 @@ struct ToolingValidateComparatorTests {
457457 )
458458 }
459459
460+ @Test ( " comparator accepts semantically equivalent numeric defaults " )
461+ func comparatorAcceptsSemanticallyEquivalentNumericDefaults( ) {
462+ let diagnostics = ToolingValidateComparator . compareQuick (
463+ expected: semanticDefaultComparisonModelIR ( ) ,
464+ actual: . init(
465+ sourceDirectory: " /virtual/Sources " ,
466+ entities: [
467+ . init(
468+ filePath: " /virtual/Sources/Item.swift " ,
469+ className: " Item " ,
470+ objcEntityName: " Item " ,
471+ persistentModelArguments: . init( generateInit: false ) ,
472+ properties: [
473+ . init(
474+ filePath: " /virtual/Sources/Item.swift " ,
475+ name: " valueMax " ,
476+ typeName: " Double " ,
477+ nonOptionalTypeName: " Double " ,
478+ declarationRange: dummyRange ( 0 , 0 ) ,
479+ declarationIndent: " " ,
480+ isOptional: false ,
481+ defaultValueLiteral: " 0 " ,
482+ defaultValueRange: dummyRange ( 0 , 1 ) ,
483+ isStored: true ,
484+ isStatic: false ,
485+ hasIgnore: false ,
486+ attribute: . init(
487+ range: dummyRange ( 0 , 0 ) ,
488+ persistentName: " value_max " ,
489+ storageMethod: nil ,
490+ transformerName: nil ,
491+ transformerTypeName: nil ,
492+ decodeFailurePolicy: nil
493+ ) ,
494+ relationshipShape: nil
495+ ) ,
496+ . init(
497+ filePath: " /virtual/Sources/Item.swift " ,
498+ name: " valueRefmax " ,
499+ typeName: " Double " ,
500+ nonOptionalTypeName: " Double " ,
501+ declarationRange: dummyRange ( 2 , 2 ) ,
502+ declarationIndent: " " ,
503+ isOptional: false ,
504+ defaultValueLiteral: " 3_000 " ,
505+ defaultValueRange: dummyRange ( 2 , 7 ) ,
506+ isStored: true ,
507+ isStatic: false ,
508+ hasIgnore: false ,
509+ attribute: . init(
510+ range: dummyRange ( 0 , 0 ) ,
511+ persistentName: " value_refmax " ,
512+ storageMethod: nil ,
513+ transformerName: nil ,
514+ transformerTypeName: nil ,
515+ decodeFailurePolicy: nil
516+ ) ,
517+ relationshipShape: nil
518+ ) ,
519+ . init(
520+ filePath: " /virtual/Sources/Item.swift " ,
521+ name: " createDate " ,
522+ typeName: " Date " ,
523+ nonOptionalTypeName: " Date " ,
524+ declarationRange: dummyRange ( 8 , 8 ) ,
525+ declarationIndent: " " ,
526+ isOptional: false ,
527+ defaultValueLiteral: " Date(timeIntervalSinceReferenceDate: 623_726_820) " ,
528+ defaultValueRange: dummyRange ( 8 , 61 ) ,
529+ isStored: true ,
530+ isStatic: false ,
531+ hasIgnore: false ,
532+ attribute: nil ,
533+ relationshipShape: nil
534+ ) ,
535+ ] ,
536+ customMembers: [ ]
537+ )
538+ ]
539+ ) ,
540+ level: . conformance
541+ )
542+
543+ #expect( diagnostics. isEmpty)
544+ }
545+
546+ @Test ( " comparator still rejects non-equivalent semantic defaults " )
547+ func comparatorRejectsNonEquivalentSemanticDefaults( ) {
548+ let diagnostics = ToolingValidateComparator . compareQuick (
549+ expected: semanticDefaultComparisonModelIR ( ) ,
550+ actual: . init(
551+ sourceDirectory: " /virtual/Sources " ,
552+ entities: [
553+ . init(
554+ filePath: " /virtual/Sources/Item.swift " ,
555+ className: " Item " ,
556+ objcEntityName: " Item " ,
557+ persistentModelArguments: . init( generateInit: false ) ,
558+ properties: [
559+ . init(
560+ filePath: " /virtual/Sources/Item.swift " ,
561+ name: " valueMax " ,
562+ typeName: " Double " ,
563+ nonOptionalTypeName: " Double " ,
564+ declarationRange: dummyRange ( 0 , 0 ) ,
565+ declarationIndent: " " ,
566+ isOptional: false ,
567+ defaultValueLiteral: " 1 " ,
568+ defaultValueRange: dummyRange ( 0 , 1 ) ,
569+ isStored: true ,
570+ isStatic: false ,
571+ hasIgnore: false ,
572+ attribute: . init(
573+ range: dummyRange ( 0 , 0 ) ,
574+ persistentName: " value_max " ,
575+ storageMethod: nil ,
576+ transformerName: nil ,
577+ transformerTypeName: nil ,
578+ decodeFailurePolicy: nil
579+ ) ,
580+ relationshipShape: nil
581+ ) ,
582+ . init(
583+ filePath: " /virtual/Sources/Item.swift " ,
584+ name: " valueRefmax " ,
585+ typeName: " Double " ,
586+ nonOptionalTypeName: " Double " ,
587+ declarationRange: dummyRange ( 2 , 2 ) ,
588+ declarationIndent: " " ,
589+ isOptional: false ,
590+ defaultValueLiteral: " 3000.0 " ,
591+ defaultValueRange: dummyRange ( 2 , 8 ) ,
592+ isStored: true ,
593+ isStatic: false ,
594+ hasIgnore: false ,
595+ attribute: . init(
596+ range: dummyRange ( 0 , 0 ) ,
597+ persistentName: " value_refmax " ,
598+ storageMethod: nil ,
599+ transformerName: nil ,
600+ transformerTypeName: nil ,
601+ decodeFailurePolicy: nil
602+ ) ,
603+ relationshipShape: nil
604+ ) ,
605+ . init(
606+ filePath: " /virtual/Sources/Item.swift " ,
607+ name: " createDate " ,
608+ typeName: " Date " ,
609+ nonOptionalTypeName: " Date " ,
610+ declarationRange: dummyRange ( 8 , 8 ) ,
611+ declarationIndent: " " ,
612+ isOptional: false ,
613+ defaultValueLiteral: " Date(timeIntervalSinceReferenceDate: 623726821.0) " ,
614+ defaultValueRange: dummyRange ( 8 , 63 ) ,
615+ isStored: true ,
616+ isStatic: false ,
617+ hasIgnore: false ,
618+ attribute: nil ,
619+ relationshipShape: nil
620+ ) ,
621+ ] ,
622+ customMembers: [ ]
623+ )
624+ ]
625+ ) ,
626+ level: . conformance
627+ )
628+
629+ #expect( diagnostics. count == 2 )
630+ #expect(
631+ diagnostics. contains {
632+ $0. message. contains ( " default value mismatch for 'Item.valueMax' " )
633+ }
634+ )
635+ #expect(
636+ diagnostics. contains {
637+ $0. message. contains ( " default value mismatch for 'Item.createDate' " )
638+ }
639+ )
640+ }
641+
460642 @Test ( " comparator can ignore optionality mismatch for optional model attribute " )
461643 func comparatorCanIgnoreOptionalityMismatchForOptionalModelAttribute( ) {
462644 let model = ToolingModelIR (
@@ -746,6 +928,93 @@ private func requiredRawStorageModelIR() -> ToolingModelIR {
746928 )
747929}
748930
931+ private func semanticDefaultComparisonModelIR( ) -> ToolingModelIR {
932+ . init(
933+ source: . init(
934+ originalPath: " /virtual/AppModel.xcdatamodeld " ,
935+ selectedSourcePath: " /virtual/AppModel.xcdatamodeld/V1.xcdatamodel " ,
936+ compiledModelPath: " /virtual/AppModel.momd " ,
937+ inputKind: . xcdatamodeld,
938+ selectedVersionName: " V1.xcdatamodel "
939+ ) ,
940+ generationPolicy: . init(
941+ accessLevel: . internal,
942+ singleFile: false ,
943+ splitByEntity: true ,
944+ generateInit: false ,
945+ defaultDecodeFailurePolicy: . fallbackToDefaultValue
946+ ) ,
947+ entities: [
948+ . init(
949+ name: " Item " ,
950+ managedObjectClassName: " NSManagedObject " ,
951+ representedClassName: " Item " ,
952+ attributes: [
953+ . init(
954+ persistentName: " value_max " ,
955+ swiftName: " valueMax " ,
956+ coreDataAttributeType: " Double " ,
957+ coreDataPrimitiveType: " Double " ,
958+ isUnique: false ,
959+ isTransient: false ,
960+ isOptional: false ,
961+ hasModelDefaultValue: true ,
962+ modelDefaultValueLiteral: " 0.0 " ,
963+ storage: . init(
964+ method: . default,
965+ swiftType: " Double " ,
966+ nonOptionalSwiftType: " Double " ,
967+ transformerName: nil ,
968+ decodeFailurePolicy: nil ,
969+ isResolved: true
970+ )
971+ ) ,
972+ . init(
973+ persistentName: " value_refmax " ,
974+ swiftName: " valueRefmax " ,
975+ coreDataAttributeType: " Double " ,
976+ coreDataPrimitiveType: " Double " ,
977+ isUnique: false ,
978+ isTransient: false ,
979+ isOptional: false ,
980+ hasModelDefaultValue: true ,
981+ modelDefaultValueLiteral: " 3000.0 " ,
982+ storage: . init(
983+ method: . default,
984+ swiftType: " Double " ,
985+ nonOptionalSwiftType: " Double " ,
986+ transformerName: nil ,
987+ decodeFailurePolicy: nil ,
988+ isResolved: true
989+ )
990+ ) ,
991+ . init(
992+ persistentName: " createDate " ,
993+ swiftName: " createDate " ,
994+ coreDataAttributeType: " Date " ,
995+ coreDataPrimitiveType: " Date " ,
996+ isUnique: false ,
997+ isTransient: false ,
998+ isOptional: false ,
999+ hasModelDefaultValue: true ,
1000+ modelDefaultValueLiteral: " Date(timeIntervalSinceReferenceDate: 623726820.0) " ,
1001+ storage: . init(
1002+ method: . default,
1003+ swiftType: " Date " ,
1004+ nonOptionalSwiftType: " Date " ,
1005+ transformerName: nil ,
1006+ decodeFailurePolicy: nil ,
1007+ isResolved: true
1008+ )
1009+ ) ,
1010+ ] ,
1011+ relationships: [ ] ,
1012+ compositions: [ ]
1013+ )
1014+ ]
1015+ )
1016+ }
1017+
7491018private func dummyRange( _ start: Int , _ end: Int ) -> ToolingTextRange {
7501019 . init( startUTF8Offset: start, endUTF8Offset: end)
7511020}
0 commit comments