Skip to content

Commit 4d5d79b

Browse files
authored
Merge pull request #75 from MercuryTechnologies/tad/generic-newtype-test
Propagate 'Codable' bounds through MoatData tyvars
2 parents 8cbdf87 + 87c52ed commit 4d5d79b

File tree

10 files changed

+116
-13
lines changed

10 files changed

+116
-13
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@Parcelize
2+
@Serializable
3+
value class LTree<A : Parcelable>(val value: List<A>) : Parcelable
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
data class Tree<A : Parcelable>(
2+
val rootLabel: A,
3+
val subForest: List<Tree<A>>,
4+
) : Parcelable
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
struct LTree<A: Hashable & Codable>: Hashable, Codable {
2+
typealias LTreeTag = Tagged<LTree, [A]>
3+
let value: LTreeTag
4+
}

.golden/swiftGenericStructSpec/golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
struct Tree<A: Hashable & Codable>: Hashable, Codable {
2+
var rootLabel: A
3+
var subForest: [Tree<A>]
4+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
struct Data<A, B>: CaseIterable, Hashable, Codable {
1+
struct Data<A: Hashable & Codable, B: Hashable & Codable>: CaseIterable, Hashable, Codable {
22
var field0: A
33
var field1: B
44
}

.golden/swiftTypeVariableSpec/golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
struct Data<A>: CaseIterable, Hashable, Codable {
1+
struct Data<A: Hashable & Codable>: CaseIterable, Hashable, Codable {
22
var field0: A
33
}

moat.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ test-suite spec
8383
DuplicateRecordFieldSpec
8484
EnumValueClassDocSpec
8585
EnumValueClassSpec
86+
GenericNewtypeSpec
87+
GenericStructSpec
8688
MultipleTypeVariableSpec
8789
StrictEnumsSpec
8890
StrictFieldsSpec

src/Moat/Pretty/Swift.hs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ prettySwiftDataWith indent = \case
2929
MoatEnum {..} ->
3030
prettyTypeDoc "" enumDoc []
3131
++ "enum "
32-
++ prettyMoatTypeHeader enumName enumTyVars
32+
++ prettyMoatTypeHeader enumName (addTyVarBounds enumTyVars enumProtocols)
3333
++ prettyRawValueAndProtocols enumRawValue enumProtocols
3434
++ " {"
3535
++ newlineNonEmpty enumCases
@@ -42,7 +42,7 @@ prettySwiftDataWith indent = \case
4242
MoatStruct {..} ->
4343
prettyTypeDoc "" structDoc []
4444
++ "struct "
45-
++ prettyMoatTypeHeader structName structTyVars
45+
++ prettyMoatTypeHeader structName (addTyVarBounds structTyVars structProtocols)
4646
++ prettyRawValueAndProtocols Nothing structProtocols
4747
++ " {"
4848
++ newlineNonEmpty structFields
@@ -55,13 +55,13 @@ prettySwiftDataWith indent = \case
5555
MoatAlias {..} ->
5656
prettyTypeDoc "" aliasDoc []
5757
++ "typealias "
58-
++ prettyMoatTypeHeader aliasName aliasTyVars
58+
++ prettyMoatTypeHeader aliasName (addTyVarBounds aliasTyVars [])
5959
++ " = "
6060
++ prettyMoatType aliasTyp
6161
MoatNewtype {..} ->
6262
prettyTypeDoc "" newtypeDoc []
6363
++ "struct "
64-
++ prettyMoatTypeHeader newtypeName newtypeTyVars
64+
++ prettyMoatTypeHeader newtypeName (addTyVarBounds newtypeTyVars newtypeProtocols)
6565
++ prettyRawValueAndProtocols Nothing newtypeProtocols
6666
++ " {\n"
6767
++ indents
@@ -112,17 +112,17 @@ prettyRawValueAndProtocols Nothing ps = ": " ++ prettyProtocols ps
112112
prettyRawValueAndProtocols (Just ty) [] = ": " ++ prettyMoatType ty
113113
prettyRawValueAndProtocols (Just ty) ps = ": " ++ prettyMoatType ty ++ ", " ++ prettyProtocols ps
114114

115+
prettyProtocol :: Protocol -> String
116+
prettyProtocol = \case
117+
Hashable -> "Hashable"
118+
Codable -> "Codable"
119+
Equatable -> "Equatable"
120+
OtherProtocol s -> s
121+
115122
prettyProtocols :: [Protocol] -> String
116123
prettyProtocols = \case
117124
[] -> ""
118125
ps -> intercalate ", " (prettyProtocol <$> ps)
119-
where
120-
prettyProtocol :: Protocol -> String
121-
prettyProtocol = \case
122-
Hashable -> "Hashable"
123-
Codable -> "Codable"
124-
Equatable -> "Equatable"
125-
OtherProtocol s -> s
126126

127127
-- TODO: Need a plan to avoid @error@ in these pure functions
128128
{-# ANN prettyTags "HLint: ignore" #-}
@@ -281,3 +281,28 @@ prettyPrivateTypes indents = go
281281
onLast :: (a -> a) -> [a] -> [a]
282282
onLast _ [] = []
283283
onLast f (x : xs) = x : map f xs
284+
285+
-- | Copy protocols from the parent type to upper bounds of generic type
286+
-- parameters.
287+
--
288+
-- This is needed for protocols with compiler-synthesized implementations
289+
-- (similar to 'deriving stock'), of which there are currently three:
290+
--
291+
-- - 'Equatable'
292+
-- - 'Hashable'
293+
-- - 'Codable'
294+
--
295+
-- See the [Swift documentation](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols#Adopting-a-Protocol-Using-a-Synthesized-Implementation).
296+
addTyVarBounds :: [String] -> [Protocol] -> [String]
297+
addTyVarBounds tyVars protos =
298+
let isSynthesized :: Protocol -> Bool
299+
isSynthesized = \case
300+
Hashable -> True
301+
Codable -> True
302+
Equatable -> True
303+
OtherProtocol _ -> False
304+
synthesizedProtos = filter isSynthesized protos
305+
bounds = ": " ++ intercalate " & " (map prettyProtocol synthesizedProtos)
306+
in case synthesizedProtos of
307+
[] -> tyVars
308+
_ -> map (++ bounds) tyVars

test/GenericNewtypeSpec.hs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{-# LANGUAGE DerivingStrategies #-}
2+
3+
module GenericNewtypeSpec where
4+
5+
import Common
6+
import Data.List.NonEmpty (NonEmpty)
7+
import Moat
8+
import Test.Hspec
9+
import Test.Hspec.Golden
10+
import Prelude
11+
12+
newtype LTree a = LTree (NonEmpty a)
13+
deriving stock (Show, Eq)
14+
15+
mobileGenWith
16+
( defaultOptions
17+
{ dataAnnotations = [Parcelize, Serializable]
18+
, dataInterfaces = [Parcelable]
19+
, dataProtocols = [Hashable, Codable]
20+
, dataRawValue = Just Str
21+
, generateDocComments = False
22+
}
23+
)
24+
''LTree
25+
26+
spec :: Spec
27+
spec =
28+
describe "stays golden" $ do
29+
let moduleName = "GenericNewtypeSpec"
30+
it "swift" $
31+
defaultGolden ("swift" <> moduleName) (showSwift @(LTree _))
32+
it "kotlin" $
33+
defaultGolden ("kotlin" <> moduleName) (showKotlin @(LTree _))

test/GenericStructSpec.hs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+
module GenericStructSpec where
4+
5+
import Common
6+
import Data.Tree (Tree)
7+
import Moat
8+
import Test.Hspec
9+
import Test.Hspec.Golden
10+
import Prelude
11+
12+
mobileGenWith
13+
( defaultOptions
14+
{ dataInterfaces = [Parcelable]
15+
, dataProtocols = [Hashable, Codable]
16+
, generateDocComments = False
17+
}
18+
)
19+
''Tree
20+
21+
spec :: Spec
22+
spec =
23+
describe "stays golden" $ do
24+
let moduleName = "GenericStructSpec"
25+
it "swift" $
26+
defaultGolden ("swift" <> moduleName) (showSwift @(Tree _))
27+
it "kotlin" $
28+
defaultGolden ("kotlin" <> moduleName) (showKotlin @(Tree _))

0 commit comments

Comments
 (0)