Merge remote-tracking branch 'origin/trunk' into wip/codebase2

but IO.handleOPs fails
# Conflicts:
#	parser-typechecker/src/Unison/Builtin.hs
#	parser-typechecker/src/Unison/Builtin/Decls.hs
#	parser-typechecker/src/Unison/Builtin/Terms.hs
#	parser-typechecker/src/Unison/Codebase.hs
#	parser-typechecker/src/Unison/Codebase/Editor/HandleInput.hs
#	parser-typechecker/unison-parser-typechecker.cabal
#	unison-src/transcripts/isPropagated-exists.md
#	unison-src/transcripts/isPropagated-exists.output.md
This commit is contained in:
Arya Irani 2021-02-11 21:59:26 -05:00
commit 93968af9ad
45 changed files with 3197 additions and 825 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
.unison*
test-output
transcript-*
scratch.u
# Stack
.stack-work

View File

@ -61,7 +61,8 @@ names0 = Names3.names0 terms types where
| (ct, (_,(r,decl))) <- ((CT.Data,) <$> builtinDataDecls @Symbol) <>
((CT.Effect,) . (second . second) DD.toDataDecl <$> builtinEffectDecls)
, ((_,vc,_), cid) <- DD.constructors' decl `zip` [0..]] <>
Rel.fromList [ (Name.fromVar v, Referent.Ref (R.DerivedId i)) | (v,i) <- Map.toList $ TD.builtinTermsRef @Symbol Intrinsic]
Rel.fromList [ (Name.fromVar v, Referent.Ref (R.DerivedId i))
| (v,i) <- Map.toList $ TD.builtinTermsRef @Symbol Intrinsic]
types = Rel.fromList builtinTypes <>
Rel.fromList [ (Name.fromVar v, R.DerivedId r)
| (v,(r,_)) <- builtinDataDecls @Symbol ] <>
@ -169,9 +170,15 @@ builtinTypesSrc =
, B' "Value" CT.Data
, B' "Any" CT.Data
, B' "crypto.HashAlgorithm" CT.Data
, B' "IOFailure" CT.Data, Rename' "IOFailure" "io2.IOFailure"
, B' "TlsFailure" CT.Data, Rename' "TlsFailure" "io2.TlsFailure"
, B' "Tls" CT.Data, Rename' "Tls" "io2.Tls"
, B' "Tls.ClientConfig" CT.Data, Rename' "Tls.ClientConfig" "io2.Tls.ClientConfig"
, B' "Tls.ServerConfig" CT.Data, Rename' "Tls.ServerConfig" "io2.Tls.ServerConfig"
, B' "Tls.SignedCert" CT.Data, Rename' "Tls.SignedCert" "io2.Tls.SignedCert"
, B' "Tls.PrivateKey" CT.Data, Rename' "Tls.PrivateKey" "io2.Tls.PrivateKey"
, B' "TVar" CT.Data, Rename' "TVar" "io2.TVar"
, B' "STM" CT.Effect, Rename' "STM" "io2.STM"
]
-- rename these to "builtin" later, when builtin means intrinsic as opposed to
@ -388,7 +395,7 @@ builtinsSrc =
, B "Text.toCharList" $ text --> list char
, B "Text.fromCharList" $ list char --> text
, B "Text.toUtf8" $ text --> bytes
, B "Text.fromUtf8.v2" $ bytes --> eithert failure text
, B "Text.fromUtf8.v3" $ bytes --> eithert failure text
, B "Char.toNat" $ char --> nat
, B "Char.fromNat" $ nat --> char
@ -443,10 +450,10 @@ builtinsSrc =
,(">=", "gteq")]
] ++ moveUnder "io2" ioBuiltins
++ moveUnder "io2" mvarBuiltins
++ moveUnder "io2" stmBuiltins
++ hashBuiltins
++ fmap (uncurry B) codeBuiltins
moveUnder :: Text -> [(Text, Type v)] -> [BuiltinDSL v]
moveUnder prefix bs = bs >>= \(n,ty) -> [B n ty, Rename n (prefix <> "." <> n)]
@ -502,65 +509,77 @@ hashBuiltins =
ioBuiltins :: Var v => [(Text, Type v)]
ioBuiltins =
[ ("IO.openFile.v2", text --> fmode --> iof handle)
, ("IO.closeFile.v2", handle --> iof unit)
, ("IO.isFileEOF.v2", handle --> iof boolean)
, ("IO.isFileOpen.v2", handle --> iof boolean)
, ("IO.isSeekable.v2", handle --> iof boolean)
, ("IO.seekHandle.v2", handle --> smode --> int --> iof unit)
, ("IO.handlePosition.v2", handle --> iof int)
, ("IO.getBuffering.v2", handle --> iof bmode)
, ("IO.setBuffering.v2", handle --> bmode --> iof unit)
, ("IO.getBytes.v2", handle --> nat --> iof bytes)
, ("IO.putBytes.v2", handle --> bytes --> iof unit)
, ("IO.systemTime.v2", unit --> iof nat)
, ("IO.getTempDirectory.v2", unit --> iof text)
[ ("IO.openFile.v3", text --> fmode --> iof handle)
, ("IO.closeFile.v3", handle --> iof unit)
, ("IO.isFileEOF.v3", handle --> iof boolean)
, ("IO.isFileOpen.v3", handle --> iof boolean)
, ("IO.isSeekable.v3", handle --> iof boolean)
, ("IO.seekHandle.v3", handle --> smode --> int --> iof unit)
, ("IO.handlePosition.v3", handle --> iof nat)
, ("IO.getBuffering.v3", handle --> iof bmode)
, ("IO.setBuffering.v3", handle --> bmode --> iof unit)
, ("IO.getBytes.v3", handle --> nat --> iof bytes)
, ("IO.putBytes.v3", handle --> bytes --> iof unit)
, ("IO.systemTime.v3", unit --> iof nat)
, ("IO.getTempDirectory.v3", unit --> iof text)
, ("IO.createTempDirectory", text --> iof text)
, ("IO.getCurrentDirectory.v2", unit --> iof text)
, ("IO.setCurrentDirectory.v2", text --> iof unit)
, ("IO.fileExists.v2", text --> iof boolean)
, ("IO.isDirectory.v2", text --> iof boolean)
, ("IO.createDirectory.v2", text --> iof unit)
, ("IO.removeDirectory.v2", text --> iof unit)
, ("IO.renameDirectory.v2", text --> text --> iof unit)
, ("IO.removeFile.v2", text --> iof unit)
, ("IO.renameFile.v2", text --> text --> iof unit)
, ("IO.getFileTimestamp.v2", text --> iof nat)
, ("IO.getFileSize.v2", text --> iof nat)
, ("IO.serverSocket.v2", text --> text --> iof socket)
, ("IO.listen.v2", socket --> iof unit)
, ("IO.clientSocket.v2", text --> text --> iof socket)
, ("IO.closeSocket.v2", socket --> iof unit)
, ("IO.socketAccept.v2", socket --> iof socket)
, ("IO.socketSend.v2", socket --> bytes --> iof unit)
, ("IO.socketReceive.v2", socket --> nat --> iof bytes)
, ("IO.getCurrentDirectory.v3", unit --> iof text)
, ("IO.setCurrentDirectory.v3", text --> iof unit)
, ("IO.fileExists.v3", text --> iof boolean)
, ("IO.isDirectory.v3", text --> iof boolean)
, ("IO.createDirectory.v3", text --> iof unit)
, ("IO.removeDirectory.v3", text --> iof unit)
, ("IO.renameDirectory.v3", text --> text --> iof unit)
, ("IO.removeFile.v3", text --> iof unit)
, ("IO.renameFile.v3", text --> text --> iof unit)
, ("IO.getFileTimestamp.v3", text --> iof nat)
, ("IO.getFileSize.v3", text --> iof nat)
, ("IO.serverSocket.v3", optionalt text --> text --> iof socket)
, ("IO.listen.v3", socket --> iof unit)
, ("IO.clientSocket.v3", text --> text --> iof socket)
, ("IO.closeSocket.v3", socket --> iof unit)
, ("IO.socketPort", socket --> iof nat)
, ("IO.socketAccept.v3", socket --> iof socket)
, ("IO.socketSend.v3", socket --> bytes --> iof unit)
, ("IO.socketReceive.v3", socket --> nat --> iof bytes)
, ("IO.forkComp.v2"
, forall1 "a" $ \a -> (unit --> iof a) --> io threadId)
, forall1 "a" $ \a -> (unit --> io a) --> io threadId)
, ("IO.stdHandle", stdhandle --> handle)
, ("IO.delay.v2", nat --> iof unit)
, ("IO.kill.v2", threadId --> iof unit)
, ("IO.delay.v3", nat --> iof unit)
, ("IO.kill.v3", threadId --> iof unit)
, ("Tls.newClient", tlsClientConfig --> socket --> iof tls)
, ("Tls.newServer", tlsServerConfig --> socket --> iof tls)
, ("Tls.handshake", tls --> iof unit)
, ("Tls.send", tls --> bytes --> iof unit)
, ("Tls.decodeCert", bytes --> eithert failure tlsSignedCert)
, ("Tls.encodeCert", tlsSignedCert --> bytes)
, ("Tls.decodePrivateKey", bytes --> list tlsPrivateKey)
, ("Tls.encodePrivateKey", tlsPrivateKey --> bytes)
, ("Tls.receive", tls --> iof bytes)
, ("Tls.terminate", tls --> iof unit)
, ("Tls.Config.defaultClient", text --> bytes --> tlsClientConfig)
, ("Tls.Config.defaultServer", tlsServerConfig)
, ("Tls.ClientConfig.default", text --> bytes --> tlsClientConfig)
, ("Tls.ServerConfig.default", list tlsSignedCert --> tlsPrivateKey --> tlsServerConfig)
, ("TLS.ClientConfig.ciphers.set", list tlsCipher --> tlsClientConfig --> tlsClientConfig)
, ("Tls.ServerConfig.ciphers.set", list tlsCipher --> tlsServerConfig --> tlsServerConfig)
, ("Tls.ClientConfig.certificates.set", list tlsSignedCert --> tlsClientConfig --> tlsClientConfig)
, ("Tls.ServerConfig.certificates.set", list tlsSignedCert --> tlsServerConfig --> tlsServerConfig)
, ("Tls.ClientConfig.versions.set", list tlsVersion --> tlsClientConfig --> tlsClientConfig)
, ("Tls.ServerConfig.versions.set", list tlsVersion --> tlsServerConfig --> tlsServerConfig)
]
mvarBuiltins :: forall v. Var v => [(Text, Type v)]
mvarBuiltins =
[ ("MVar.new", forall1 "a" $ \a -> a --> io (mvar a))
, ("MVar.newEmpty.v2", forall1 "a" $ \a -> unit --> io (mvar a))
, ("MVar.take.v2", forall1 "a" $ \a -> mvar a --> iof a)
, ("MVar.take.v3", forall1 "a" $ \a -> mvar a --> iof a)
, ("MVar.tryTake", forall1 "a" $ \a -> mvar a --> io (optionalt a))
, ("MVar.put.v2", forall1 "a" $ \a -> mvar a --> a --> iof unit)
, ("MVar.put.v3", forall1 "a" $ \a -> mvar a --> a --> iof unit)
, ("MVar.tryPut", forall1 "a" $ \a -> mvar a --> a --> io boolean)
, ("MVar.swap.v2", forall1 "a" $ \a -> mvar a --> a --> iof a)
, ("MVar.swap.v3", forall1 "a" $ \a -> mvar a --> a --> iof a)
, ("MVar.isEmpty", forall1 "a" $ \a -> mvar a --> io boolean)
, ("MVar.read.v2", forall1 "a" $ \a -> mvar a --> iof a)
, ("MVar.read.v3", forall1 "a" $ \a -> mvar a --> iof a)
, ("MVar.tryRead", forall1 "a" $ \a -> mvar a --> io (optionalt a))
]
where
@ -583,6 +602,18 @@ codeBuiltins =
, forall1 "a" $ \a -> value --> io (eithert (list termLink) a))
]
stmBuiltins :: forall v. Var v => [(Text, Type v)]
stmBuiltins =
[ ("TVar.new", forall1 "a" $ \a -> a --> stm (tvar a))
, ("TVar.newIO", forall1 "a" $ \a -> a --> io (tvar a))
, ("TVar.read", forall1 "a" $ \a -> tvar a --> stm a)
, ("TVar.readIO", forall1 "a" $ \a -> tvar a --> io a)
, ("TVar.write", forall1 "a" $ \a -> tvar a --> a --> stm unit)
, ("TVar.swap", forall1 "a" $ \a -> tvar a --> a --> stm a)
, ("STM.retry", forall1 "a" $ \a -> unit --> stm a)
, ("STM.atomically", forall1 "a" $ \a -> (unit --> stm a) --> io a)
]
forall1 :: Var v => Text -> (Type v -> Type v) -> Type v
forall1 name body =
let
@ -625,12 +656,14 @@ threadId = Type.threadId ()
handle = Type.fileHandle ()
unit = DD.unitType ()
tls, tlsClientConfig, tlsServerConfig :: Var v => Type v
tls, tlsClientConfig, tlsServerConfig, tlsSignedCert, tlsPrivateKey, tlsVersion, tlsCipher :: Var v => Type v
tls = Type.ref () Type.tlsRef
tlsClientConfig = Type.ref () Type.tlsClientConfigRef
tlsServerConfig = Type.ref () Type.tlsServerConfigRef
-- tlsVersion = Type.ref () Type.tlsVersionRef
-- tlsCiphers = Type.ref () Type.tlsCiphersRef
tlsSignedCert = Type.ref () Type.tlsSignedCertRef
tlsPrivateKey = Type.ref () Type.tlsPrivateKeyRef
tlsVersion = Type.ref () Type.tlsVersionRef
tlsCipher = Type.ref () Type.tlsCipherRef
fmode, bmode, smode, stdhandle :: Var v => Type v
fmode = DD.fileModeType ()
@ -653,3 +686,6 @@ code = Type.code ()
value = Type.value ()
termLink = Type.termLink ()
stm, tvar :: Var v => Type v -> Type v
stm = Type.effect1 () (Type.ref () Type.stmRef)
tvar a = Type.ref () Type.tvarRef `app` a

View File

@ -43,15 +43,18 @@ pairRef = lookupDeclRef "Tuple"
optionalRef = lookupDeclRef "Optional"
eitherRef = lookupDeclRef "Either"
testResultRef, linkRef, docRef, isPropagatedRef, isTestRef, ioErrorRef, stdHandleRef, failureRef :: Reference
testResultRef, linkRef, docRef, ioErrorRef, stdHandleRef, failureRef, tlsSignedCertRef, tlsPrivateKeyRef :: Reference
isPropagatedRef, isTestRef :: Reference
isPropagatedRef = lookupDeclRef "IsPropagated"
isTestRef = lookupDeclRef "IsTest"
testResultRef = lookupDeclRef "Test.Result"
linkRef = lookupDeclRef "Link"
docRef = lookupDeclRef "Doc"
isPropagatedRef = lookupDeclRef "IsPropagated"
isTestRef = lookupDeclRef "IsTest"
ioErrorRef = lookupDeclRef "io2.IOError"
stdHandleRef = lookupDeclRef "io2.StdHandle"
failureRef = lookupDeclRef "io2.Failure"
tlsSignedCertRef = lookupDeclRef "io2.Tls.SignedCert"
tlsPrivateKeyRef = lookupDeclRef "io2.Tls.PrivateKey"
fileModeRef, filePathRef, bufferModeRef, seekModeRef, seqViewRef :: Reference
fileModeRef = lookupDeclRef "io2.FileMode"
@ -69,7 +72,8 @@ constructorId ref name = do
(_,_,dd) <- find (\(_,r,_) -> Reference.DerivedId r == ref) (builtinDataDecls @Symbol)
elemIndex name $ DD.constructorNames dd
isPropagatedConstructorId, isTestConstructorId, okConstructorId, failConstructorId, docBlobId, docLinkId, docSignatureId, docSourceId, docEvaluateId, docJoinId, linkTermId, linkTypeId :: ConstructorId
okConstructorId, failConstructorId, docBlobId, docLinkId, docSignatureId, docSourceId, docEvaluateId, docJoinId, linkTermId, linkTypeId :: ConstructorId
isPropagatedConstructorId, isTestConstructorId :: ConstructorId
Just isPropagatedConstructorId = constructorId isPropagatedRef "IsPropagated.IsPropagated"
Just isTestConstructorId = constructorId isTestRef "IsTest.IsTest"
Just okConstructorId = constructorId testResultRef "Test.Result.Ok"
@ -91,221 +95,193 @@ failConstructorReferent = Referent.Con testResultRef failConstructorId CT.Data
-- | builtinTypes' and those types defined herein
builtinDataDecls :: Var v => [(v, Reference.Id, DataDeclaration v ())]
builtinDataDecls = rs1 ++ rs
where
rs1 = case hashDecls $
Map.fromList
[ (v "Link", link)
] of
Right a -> a
Left e -> error $ "builtinDataDecls: " <> show e
rs = case hashDecls $
Map.fromList
[ (v "Unit", unit),
(v "Tuple", tuple),
(v "Optional", opt),
(v "Either", eith),
(v "Test.Result", tr),
(v "IsPropagated", isPropagated),
(v "IsTest", isTest),
(v "Doc", doc),
(v "io2.FileMode", fmode),
(v "io2.BufferMode", bmode),
(v "io2.SeekMode", smode),
(v "SeqView", seqview),
(v "io2.IOError", ioerr),
(v "io2.StdHandle", stdhnd),
(v "io2.Failure", failure),
(v "io2.TlsFailure", tlsFailure)
] of
Right a -> a
Left e -> error $ "builtinDataDecls: " <> show e
[(_, linkRef, _)] = rs1
v = Var.named
var name = Type.var () (v name)
arr = Type.arrow'
-- see note on `hashDecls` above for why ctor must be called `Unit.Unit`.
unit = DataDeclaration Structural () [] [((), v "Unit.Unit", var "Unit")]
tuple =
DataDeclaration
Structural
where
rs1 = case hashDecls $ Map.fromList
[ (v "Link" , link)
] of Right a -> a; Left e -> error $ "builtinDataDecls: " <> show e
rs = case hashDecls $ Map.fromList
[ (v "Unit" , unit)
, (v "Tuple" , tuple)
, (v "Optional" , opt)
, (v "Either" , eith)
, (v "Test.Result" , tr)
, (v "IsPropagated" , isPropagated)
, (v "IsTest" , isTest)
, (v "Doc" , doc)
, (v "io2.FileMode" , fmode)
, (v "io2.BufferMode" , bmode)
, (v "io2.SeekMode" , smode)
, (v "SeqView" , seqview)
, (v "io2.IOError" , ioerr)
, (v "io2.StdHandle" , stdhnd)
, (v "io2.Failure" , failure)
, (v "io2.TlsFailure" , tlsFailure)
, (v "io2.IOFailure" , ioFailure)
] of Right a -> a; Left e -> error $ "builtinDataDecls: " <> show e
[(_, linkRef, _)] = rs1
v = Var.named
var name = Type.var () (v name)
arr = Type.arrow'
-- see note on `hashDecls` above for why ctor must be called `Unit.Unit`.
unit = DataDeclaration Structural () [] [((), v "Unit.Unit", var "Unit")]
tuple = DataDeclaration
Structural
()
[v "a", v "b"]
[ ( ()
, v "Tuple.Cons"
, Type.foralls
()
[v "a", v "b"]
[ ( (),
v "Tuple.Cons",
Type.foralls
()
[v "a", v "b"]
( var "a"
`arr` (var "b" `arr` Type.apps' (var "Tuple") [var "a", var "b"])
)
)
]
opt =
DataDeclaration
Structural
()
[v "a"]
[ ( (),
v "Optional.None",
Type.foralls () [v "a"] (Type.app' (var "Optional") (var "a"))
),
( (),
v "Optional.Some",
Type.foralls
()
[v "a"]
(var "a" `arr` Type.app' (var "Optional") (var "a"))
)
]
eith =
DataDeclaration
Structural
()
[v "a", v "b"]
[ ( (),
v "Either.Left",
Type.foralls
()
[v "a", v "b"]
(var "a" `arr` Type.apps' (var "Either") [var "a", var "b"])
),
( (),
v "Either.Right",
Type.foralls
()
[v "a", v "b"]
(var "b" `arr` Type.apps' (var "Either") [var "a", var "b"])
)
]
isTest =
DataDeclaration
(Unique "e6dca08b40458b03ca1660cfbdaecaa7279b42d18257898b5fd1c34596aac36f")
()
[]
[((), v "IsTest.IsTest", var "IsTest")]
isPropagated =
DataDeclaration
(Unique "b28d929d0a73d2c18eac86341a3bb9399f8550c11b5f35eabb2751e6803ccc20")
()
[]
[((), v "IsPropagated.IsPropagated", var "IsPropagated")]
fmode =
DataDeclaration
(Unique "3c11ba4f0a5d8fedd427b476cdd2d7673197d11e")
()
[]
[ ((), v "io2.FileMode.Read", var "io2.FileMode"),
((), v "io2.FileMode.Write", var "io2.FileMode"),
((), v "io2.FileMode.Append", var "io2.FileMode"),
((), v "io2.FileMode.ReadWrite", var "io2.FileMode")
]
bmode =
DataDeclaration
(Unique "7dd9560d3826c21e5e6a7e08f575b61adcddf849")
()
[]
[ ((), v "io2.BufferMode.NoBuffering", var "io2.BufferMode"),
((), v "io2.BufferMode.LineBuffering", var "io2.BufferMode"),
((), v "io2.BufferMode.BlockBuffering", var "io2.BufferMode"),
( (),
v "io2.BufferMode.SizedBlockBuffering",
Type.nat () `arr` var "io2.BufferMode"
)
]
smode =
DataDeclaration
(Unique "453a764f73cb4c7371d9af23b2d5ed646bf9e57c")
()
[]
[ ((), v "io2.SeekMode.AbsoluteSeek", var "io2.SeekMode"),
((), v "io2.SeekMode.RelativeSeek", var "io2.SeekMode"),
((), v "io2.SeekMode.SeekFromEnd", var "io2.SeekMode")
]
ioerr =
DataDeclaration
(Unique "5915e25ac83205f7885395cc6c6c988bc5ec69a1")
()
[]
[ ((), v "io2.IOError.AlreadyExists", var "io2.IOError"),
((), v "io2.IOError.NoSuchThing", var "io2.IOError"),
((), v "io2.IOError.ResourceBusy", var "io2.IOError"),
((), v "io2.IOError.ResourceExhausted", var "io2.IOError"),
((), v "io2.IOError.EOF", var "io2.IOError"),
((), v "io2.IOError.IllegalOperation", var "io2.IOError"),
((), v "io2.IOError.PermissionDenied", var "io2.IOError"),
((), v "io2.IOError.UserError", var "io2.IOError")
]
failure =
DataDeclaration
(Unique "52ad89274a358b9c802792aa05915e25ac83205f7885395cc6c6c988bc5ec69a1")
()
[]
[ ((), v "io2.Failure.Failure", (Type.typeLink () `arr` (Type.text () `arr` var "io2.Failure")))
]
tlsFailure =
DataDeclaration
(Unique "df5ba835130b227ab83d02d1feff5402455a732d613b51dee32230d2f2d067c6")
()
[]
[]
stdhnd =
DataDeclaration
(Unique "67bf7a8e517cbb1e9f42bc078e35498212d3be3c")
()
[]
[ ((), v "io2.StdHandle.StdIn", var "io2.StdHandle"),
((), v "io2.StdHandle.StdOut", var "io2.StdHandle"),
((), v "io2.StdHandle.StdErr", var "io2.StdHandle")
]
seqview =
DataDeclaration
Structural
()
[v "a", v "b"]
[ ( (),
v "SeqView.VEmpty",
Type.foralls
()
[v "a", v "b"]
(Type.apps' (var "SeqView") [var "a", var "b"])
),
( (),
v "SeqView.VElem",
let sv = Type.apps' (var "SeqView") [var "a", var "b"]
in Type.foralls
()
[v "a", v "b"]
(var "a" `arr` (var "b" `arr` sv))
)
]
tr =
DataDeclaration
(Unique "70621e539cd802b2ad53105697800930411a3ebc")
()
[]
[ ((), v "Test.Result.Fail", Type.text () `arr` var "Test.Result"),
((), v "Test.Result.Ok", Type.text () `arr` var "Test.Result")
]
doc =
DataDeclaration
(Unique "c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004")
()
[]
[ ((), v "Doc.Blob", Type.text () `arr` var "Doc"),
((), v "Doc.Link", Type.refId () linkRef `arr` var "Doc"),
((), v "Doc.Signature", Type.termLink () `arr` var "Doc"),
((), v "Doc.Source", Type.refId () linkRef `arr` var "Doc"),
((), v "Doc.Evaluate", Type.termLink () `arr` var "Doc"),
((), v "Doc.Join", Type.app () (Type.vector ()) (var "Doc") `arr` var "Doc")
]
link =
DataDeclaration
(Unique "a5803524366ead2d7f3780871d48771e8142a3b48802f34a96120e230939c46bd5e182fcbe1fa64e9bff9bf741f3c04")
()
[]
[ ((), v "Link.Term", Type.termLink () `arr` var "Link"),
((), v "Link.Type", Type.typeLink () `arr` var "Link")
]
( var "a"
`arr` (var "b" `arr` Type.apps' (var "Tuple") [var "a", var "b"])
)
)
]
opt = DataDeclaration
Structural
()
[v "a"]
[ ( ()
, v "Optional.None"
, Type.foralls () [v "a"] (Type.app' (var "Optional") (var "a"))
)
, ( ()
, v "Optional.Some"
, Type.foralls ()
[v "a"]
(var "a" `arr` Type.app' (var "Optional") (var "a"))
)
]
eith = DataDeclaration
Structural
()
[v "a", v "b"]
[ ( ()
, v "Either.Left"
, Type.foralls () [v "a", v "b"]
(var "a" `arr` Type.apps' (var "Either") [var "a", var "b"])
)
, ( ()
, v "Either.Right"
, Type.foralls () [v "a", v "b"]
(var "b" `arr` Type.apps' (var "Either") [var "a", var "b"])
)
]
isTest =
DataDeclaration
(Unique "e6dca08b40458b03ca1660cfbdaecaa7279b42d18257898b5fd1c34596aac36f")
()
[]
[((), v "IsTest.IsTest", var "IsTest")]
isPropagated =
DataDeclaration
(Unique "b28d929d0a73d2c18eac86341a3bb9399f8550c11b5f35eabb2751e6803ccc20")
()
[]
[((), v "IsPropagated.IsPropagated", var "IsPropagated")]
fmode = DataDeclaration
(Unique "3c11ba4f0a5d8fedd427b476cdd2d7673197d11e")
()
[]
[ ((), v "io2.FileMode.Read", var "io2.FileMode")
, ((), v "io2.FileMode.Write", var "io2.FileMode")
, ((), v "io2.FileMode.Append", var "io2.FileMode")
, ((), v "io2.FileMode.ReadWrite", var "io2.FileMode")
]
bmode = DataDeclaration
(Unique "7dd9560d3826c21e5e6a7e08f575b61adcddf849")
()
[]
[ ((), v "io2.BufferMode.NoBuffering", var "io2.BufferMode")
, ((), v "io2.BufferMode.LineBuffering", var "io2.BufferMode")
, ((), v "io2.BufferMode.BlockBuffering", var "io2.BufferMode")
, ((), v "io2.BufferMode.SizedBlockBuffering"
, Type.nat () `arr` var "io2.BufferMode")
]
smode = DataDeclaration
(Unique "453a764f73cb4c7371d9af23b2d5ed646bf9e57c")
()
[]
[ ((), v "io2.SeekMode.AbsoluteSeek", var "io2.SeekMode")
, ((), v "io2.SeekMode.RelativeSeek", var "io2.SeekMode")
, ((), v "io2.SeekMode.SeekFromEnd", var "io2.SeekMode")
]
ioerr = DataDeclaration
(Unique "5915e25ac83205f7885395cc6c6c988bc5ec69a1")
()
[]
[ ((), v "io2.IOError.AlreadyExists", var "io2.IOError")
, ((), v "io2.IOError.NoSuchThing", var "io2.IOError")
, ((), v "io2.IOError.ResourceBusy", var "io2.IOError")
, ((), v "io2.IOError.ResourceExhausted", var "io2.IOError")
, ((), v "io2.IOError.EOF", var "io2.IOError")
, ((), v "io2.IOError.IllegalOperation", var "io2.IOError")
, ((), v "io2.IOError.PermissionDenied", var "io2.IOError")
, ((), v "io2.IOError.UserError", var "io2.IOError")
]
failure = DataDeclaration
(Unique "52ad89274a358b9c802792aa05915e25ac83205f7885395cc6c6c988bc5ec69a1")
()
[]
[ ((), v "io2.Failure.Failure", Type.typeLink () `arr` (Type.text () `arr` (Type.any () `arr` var "io2.Failure")))
]
tlsFailure = DataDeclaration (Unique "df5ba835130b227ab83d02d1feff5402455a732d613b51dee32230d2f2d067c6")()[]
[]
ioFailure = DataDeclaration (Unique "009cb00e78cac9e47485cc3633c7a363939f63866ea07ab330346a2121d69a83")()[]
[]
stdhnd = DataDeclaration
(Unique "67bf7a8e517cbb1e9f42bc078e35498212d3be3c")
()
[]
[ ((), v "io2.StdHandle.StdIn", var "io2.StdHandle")
, ((), v "io2.StdHandle.StdOut", var "io2.StdHandle")
, ((), v "io2.StdHandle.StdErr", var "io2.StdHandle")
]
seqview = DataDeclaration
Structural
()
[v "a", v "b"]
[ ( ()
, v "SeqView.VEmpty"
, Type.foralls () [v "a", v "b"]
(Type.apps' (var "SeqView") [var "a", var "b"])
)
, ( ()
, v "SeqView.VElem"
, let sv = Type.apps' (var "SeqView") [var "a", var "b"]
in Type.foralls () [v "a", v "b"]
(var "a" `arr` (var "b" `arr` sv))
)
]
tr = DataDeclaration
(Unique "70621e539cd802b2ad53105697800930411a3ebc")
()
[]
[ ((), v "Test.Result.Fail", Type.text () `arr` var "Test.Result")
, ((), v "Test.Result.Ok" , Type.text () `arr` var "Test.Result")
]
doc = DataDeclaration
(Unique "c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004")
()
[]
[ ((), v "Doc.Blob", Type.text () `arr` var "Doc")
, ((), v "Doc.Link", Type.refId () linkRef `arr` var "Doc")
, ((), v "Doc.Signature", Type.termLink () `arr` var "Doc")
, ((), v "Doc.Source", Type.refId () linkRef `arr` var "Doc")
, ((), v "Doc.Evaluate", Type.termLink () `arr` var "Doc")
, ((), v "Doc.Join", Type.app () (Type.vector()) (var "Doc") `arr` var "Doc")
]
link = DataDeclaration
(Unique "a5803524366ead2d7f3780871d48771e8142a3b48802f34a96120e230939c46bd5e182fcbe1fa64e9bff9bf741f3c04")
()
[]
[ ((), v "Link.Term", Type.termLink () `arr` var "Link")
, ((), v "Link.Type", Type.typeLink () `arr` var "Link")
]
builtinEffectDecls :: [(v, Reference.Id, DD.EffectDeclaration v ())]
builtinEffectDecls = []
@ -355,6 +331,9 @@ seekModeType a = Type.ref a seekModeRef
stdHandleType a = Type.ref a stdHandleRef
failureType a = Type.ref a failureRef
tlsSignedCertType :: Var v => a -> Type v a
tlsSignedCertType a = Type.ref a tlsSignedCertRef
unitTerm :: Var v => a -> Term v a
unitTerm ann = Term.constructor ann unitRef 0

View File

@ -31,4 +31,6 @@ v :: Var v => Text -> v
v = Var.named
builtinTermsRef :: Var v => a -> Map v Reference.Id
builtinTermsRef a = fmap fst . Term.hashComponents . Map.fromList . fmap (\(v, tm, _tp) -> (v, tm)) $ builtinTermsSrc a
builtinTermsRef a = fmap fst . Term.hashComponents . Map.fromList
. fmap (\(v, tm, _tp) -> (v, tm))
$ builtinTermsSrc a

View File

@ -42,7 +42,7 @@ type DataDeclaration v a = DD.DataDeclaration v a
type EffectDeclaration v a = DD.EffectDeclaration v a
-- | this FileCodebase detail lives here, because the interface depends on it 🙃
type CodebasePath = FilePath
type CodebasePath = FilePath
data Codebase m v a =
Codebase { getTerm :: Reference.Id -> m (Maybe (Term v a))
@ -97,16 +97,16 @@ data GetRootBranchError
debug :: Bool
debug = False
data SyncFileCodebaseResult = SyncOk | UnknownDestinationRootBranch Branch.Hash | NotFastForward
-- | Write all of the builtins types into the codebase and create empty namespace
initializeCodebase :: forall m. Monad m => Codebase m Symbol Parser.Ann -> m ()
initializeCodebase c = do
let uf = UF.typecheckedUnisonFile (Map.fromList Builtin.builtinDataDecls)
(Map.fromList Builtin.builtinEffectDecls)
[Builtin.builtinTermsSrc Parser.Intrinsic]
mempty
let uf = (UF.typecheckedUnisonFile (Map.fromList Builtin.builtinDataDecls)
(Map.fromList Builtin.builtinEffectDecls)
[Builtin.builtinTermsSrc Parser.Intrinsic]
mempty)
addDefsToCodebase c uf
putRootBranch c (Branch.one Branch.empty0)

View File

@ -118,7 +118,7 @@ import Unison.LabeledDependency (LabeledDependency)
import Unison.Term (Term)
import Unison.Type (Type)
import qualified Unison.Builtin as Builtin
import qualified Unison.Builtin.Terms as Builtin
import qualified Unison.Builtin.Terms as Builtin
import Unison.NameSegment (NameSegment(..))
import qualified Unison.NameSegment as NameSegment
import Unison.Codebase.ShortBranchHash (ShortBranchHash)
@ -371,11 +371,9 @@ loop = do
(misses', hits) <- hqNameQuery [from]
let tpRefs = Set.fromList $ typeReferences hits
tmRefs = Set.fromList $ termReferences hits
tmMisses = misses'
<> (HQ'.toHQ . SR.termName <$> termResults hits)
tpMisses = misses'
<> (HQ'.toHQ . SR.typeName <$> typeResults hits)
misses = if isTerm then tpMisses else tmMisses
misses = Set.difference (Set.fromList misses') if isTerm
then Set.fromList $ HQ'.toHQ . SR.termName <$> termResults hits
else Set.fromList $ HQ'.toHQ . SR.typeName <$> typeResults hits
go :: Reference -> Action m (Either Event Input) v ()
go fr = do
let termPatch =
@ -391,8 +389,8 @@ loop = do
(const (if isTerm then termPatch else typePatch)))
-- Say something
success
unless (null misses) $
respond $ SearchTermsNotFound misses
unless (Set.null misses) $
respond $ SearchTermsNotFound (Set.toList misses)
traverse_ go (if isTerm then tmRefs else tpRefs)
branchExists dest _x = respond $ BranchAlreadyExists dest
branchExistsSplit = branchExists . Path.unsplit'
@ -1629,8 +1627,8 @@ loop = do
e <- eval $ Execute ppe unisonFile
case e of
Left e -> respond $ EvaluationFailure e
Right _ -> pure () -- TODO
Left e -> respond $ EvaluationFailure e
Right _ -> pure () -- TODO
IOTestI main -> do
testType <- eval RuntimeTest
@ -1662,7 +1660,7 @@ loop = do
tm' <- eval $ Evaluate1 ppe tm
case tm' of
Left e -> respond (EvaluationFailure e)
Right tm' ->
Right tm' ->
respond $ TestResults Output.NewlyComputed ppe True True (oks [(ref, tm')]) (fails [(ref, tm')])
_ -> respond $ NoMainFunction "main" ppe [testType]
_ -> respond $ NoMainFunction "main" ppe [testType]

View File

@ -839,6 +839,8 @@ data POp
| VALU -- value
-- Debug
| PRNT | INFO
-- STM
| ATOM
deriving (Show,Eq,Ord)
type ANormal = ABTN.Term ANormalF

View File

@ -29,7 +29,8 @@ import Unison.Symbol
import qualified Unison.Runtime.Stack as Closure
import Unison.Runtime.Stack (Closure)
import Unison.Runtime.Foreign
( Foreign(Wrap), HashAlgorithm(..), Failure(..))
( Foreign(Wrap), HashAlgorithm(..), pattern Failure)
import qualified Unison.Runtime.Foreign as F
import Unison.Runtime.Foreign.Function
import Unison.Runtime.IOSource
@ -48,7 +49,10 @@ import Data.Text.Encoding ( decodeUtf8', decodeUtf8' )
import qualified Data.ByteArray as BA
import qualified Data.ByteString.Lazy as L
import qualified System.X509 as X
import qualified Data.X509 as X
import qualified Data.X509.Memory as X
import qualified Data.X509.CertificateStore as X
import Data.PEM (pemContent, pemParseLBS, PEM)
import Data.Set (insert)
import qualified Data.Map as Map
@ -56,6 +60,7 @@ import Unison.Prelude
import qualified Unison.Util.Bytes as Bytes
import Network.Socket as SYS
( accept
, socketPort
, Socket
)
import Network.Simple.TCP as SYS
@ -71,7 +76,8 @@ import Network.TLS as TLS
import Network.TLS.Extra.Cipher as Cipher
import System.IO as SYS
( openFile
( IOMode(..)
, openFile
, hClose
, hGetBuffering
, hSetBuffering
@ -108,6 +114,13 @@ import System.Directory as SYS
)
import System.IO.Temp (createTempDirectory)
import qualified Control.Concurrent.STM as STM
import qualified GHC.Conc as STM
import GHC.IO (IO(IO))
type Failure = F.Failure Closure
freshes :: Var v => Int -> [v]
freshes = freshes' mempty
@ -686,6 +699,16 @@ value'load
value'create :: Var v => SuperNormal v
value'create = unop0 0 $ \[x] -> TPrm VALU [x]
stm'atomic :: Var v => SuperNormal v
stm'atomic
= Lambda [BX]
. TAbs act
. TLetD unit BX (TCon Ty.unitRef 0 [])
. TName lz (Right act) [unit]
$ TPrm ATOM [lz]
where
(act,unit,lz) = fresh3
type ForeignOp = forall v. Var v => FOp -> ([Mem], ANormal v)
standard'handle :: ForeignOp
@ -789,6 +812,19 @@ inNat arg nat result cont instr =
. unbox arg Ty.natRef nat
$ TLetD result UN (TFOp instr [nat]) cont
-- Maybe a -> b -> ...
inMaybeBx :: forall v. Var v => v -> v -> v -> v -> v -> ANormal v -> FOp -> ([Mem], ANormal v)
inMaybeBx arg1 arg2 arg3 mb result cont instr =
([BX, BX],)
. TAbss [arg1, arg2]
. TMatch arg1 . flip (MatchData Ty.optionalRef) Nothing
$ mapFromList
[ (0, ([], TLetD mb UN (TLit $ I 0)
$ TLetD result UN (TFOp instr [mb, arg2]) cont))
, (1, ([BX], TAbs arg3 . TLetD mb UN (TLit $ I 1) $ TLetD result UN (TFOp instr [mb, arg3, arg2]) cont))
]
-- a -> b -> ...
inBxBx :: forall v. Var v => v -> v -> v -> ANormal v -> FOp -> ([Mem], ANormal v)
inBxBx arg1 arg2 result cont instr =
@ -823,9 +859,6 @@ inBxIomr arg1 arg2 fm result cont instr
-- All of these functions will take a Var named result containing the
-- result of the foreign call
--
outInt :: forall v. Var v => v -> ANormal v
outInt i = TCon Ty.intRef 0 [i]
outMaybe :: forall v. Var v => v -> v -> ANormal v
outMaybe maybe result =
TMatch result . MatchSum $ mapFromList
@ -852,7 +885,7 @@ outIoFailNat stack1 stack2 stack3 fail nat result =
$ TCon eitherReference 0 [fail])
, (1, ([UN],)
. TAbs stack3
. TLetD nat UN (TCon Ty.natRef 0 [stack3])
. TLetD nat BX (TCon Ty.natRef 0 [stack3])
$ TCon eitherReference 1 [nat])
]
@ -932,14 +965,6 @@ unitToEFBox = inUnit unit result
$ outIoFailBox stack1 stack2 fail result
where (unit, stack1, stack2, fail, result) = fresh5
-- a -> Int
boxToInt :: ForeignOp
boxToInt = inBx arg result
$ outInt result
where
(arg, result) = fresh2
-- a -> IOMode -> Either Failure b
boxIomrToEFBox :: ForeignOp
boxIomrToEFBox = inBxIomr arg1 arg2 enum result
$ outIoFailBox stack1 stack2 fail result
@ -952,6 +977,16 @@ boxTo0 = inBx arg result (TCon Ty.unitRef 0 [])
where
(arg, result) = fresh2
-- a -> b ->{E} ()
boxBoxTo0 :: ForeignOp
boxBoxTo0 instr
= ([BX,BX],)
. TAbss [arg1,arg2]
. TLets Direct [] [] (TFOp instr [arg1,arg2])
$ TCon Ty.unitRef 0 []
where
(arg1, arg2) = fresh2
-- Nat -> ()
natToUnit :: ForeignOp
natToUnit = inNat arg nat result (TCon Ty.unitRef 0 [])
@ -1033,6 +1068,14 @@ boxToEFNat = inBx arg result
where
(arg, result, stack1, stack2, stack3, nat, fail) = fresh7
-- Maybe a -> b -> Either Failure c
maybeBoxToEFBox :: ForeignOp
maybeBoxToEFBox = inMaybeBx arg1 arg2 arg3 mb result
$ outIoFail stack1 stack2 fail result
where
(arg1, arg2, arg3, mb, result, stack1, stack2, fail) = fresh8
-- a -> b -> Either Failure c
boxBoxToEFBox :: ForeignOp
boxBoxToEFBox = inBxBx arg1 arg2 result
@ -1228,13 +1271,15 @@ builtinLookup
, ("jumpCont", jumpk)
, ("IO.forkComp", fork'comp)
, ("IO.forkComp.v2", fork'comp)
, ("Code.isMissing", code'missing)
, ("Code.cache_", code'cache)
, ("Code.lookup", code'lookup)
, ("Value.load", value'load)
, ("Value.value", value'create)
, ("STM.atomically", stm'atomic)
] ++ foreignWrappers
type FDecl v
@ -1254,9 +1299,12 @@ mkForeignIOF f = mkForeign $ \a -> tryIOE (f a)
tryIOE :: IO a -> IO (Either Failure a)
tryIOE = fmap handleIOE . try
handleIOE :: Either IOException a -> Either Failure a
handleIOE (Left e) = Left $ Failure ioFailureReference (pack (show e))
handleIOE (Left e) = Left $ Failure ioFailureReference (pack (show e)) unitValue
handleIOE (Right a) = Right a
unitValue :: Closure
unitValue = Closure.Enum Ty.unitRef 0
mkForeignTls
:: forall a r.(ForeignConvention a, ForeignConvention r)
=> (a -> IO r) -> ForeignFunc
@ -1266,40 +1314,48 @@ mkForeignTls f = mkForeign $ \a -> fmap flatten (tryIO2 (tryIO1 (f a)))
tryIO1 = try
tryIO2 :: IO (Either TLS.TLSException r) -> IO (Either IOException (Either TLS.TLSException r))
tryIO2 = try
flatten :: Either IOException (Either TLS.TLSException r) -> Either Failure r
flatten (Left e) = Left (Failure ioFailureReference (pack (show e)))
flatten (Right (Left e)) = Left (Failure tlsFailureReference (pack (show e)))
flatten :: Either IOException (Either TLS.TLSException r) -> Either (Failure ) r
flatten (Left e) = Left (Failure ioFailureReference (pack (show e)) unitValue)
flatten (Right (Left e)) = Left (Failure tlsFailureReference (pack (show e)) (unitValue))
flatten (Right (Right a)) = Right a
declareForeigns :: Var v => FDecl v ()
declareForeigns = do
declareForeign "IO.openFile.v3" boxIomrToEFBox $
mkForeignIOF $ \(fnameText :: Text, n :: Int) ->
let fname = (unpack fnameText)
mode = case n of
0 -> ReadMode
1 -> WriteMode
2 -> AppendMode
_ -> ReadWriteMode
in openFile fname mode
declareForeign "IO.openFile.v2" boxIomrToEFBox $ mkForeignIOF (uncurry openFile)
declareForeign "IO.closeFile.v2" boxToEF0 $ mkForeignIOF hClose
declareForeign "IO.isFileEOF.v2" boxToEFBool $ mkForeignIOF hIsEOF
declareForeign "IO.isFileOpen.v2" boxToEFBool $ mkForeignIOF hIsOpen
declareForeign "IO.isSeekable.v2" boxToEFBool $ mkForeignIOF hIsSeekable
declareForeign "IO.closeFile.v3" boxToEF0 $ mkForeignIOF hClose
declareForeign "IO.isFileEOF.v3" boxToEFBool $ mkForeignIOF hIsEOF
declareForeign "IO.isFileOpen.v3" boxToEFBool $ mkForeignIOF hIsOpen
declareForeign "IO.isSeekable.v3" boxToEFBool $ mkForeignIOF hIsSeekable
declareForeign "IO.seekHandle.v2" seek'handle
declareForeign "IO.seekHandle.v3" seek'handle
. mkForeignIOF $ \(h,sm,n) -> hSeek h sm (fromIntegral (n :: Int))
declareForeign "IO.handlePosition.v2" boxToInt
declareForeign "IO.handlePosition.v3" boxToEFNat
-- TODO: truncating integer
. mkForeignIOF $ \h -> fromInteger @Word64 <$> hTell h
declareForeign "IO.getBuffering.v2" get'buffering
declareForeign "IO.getBuffering.v3" get'buffering
$ mkForeignIOF hGetBuffering
declareForeign "IO.setBuffering.v2" boxBoxToEF0
declareForeign "IO.setBuffering.v3" boxBoxToEF0
. mkForeignIOF $ uncurry hSetBuffering
declareForeign "IO.getBytes.v2" boxNatToEFBox . mkForeignIOF $ \(h,n) -> Bytes.fromArray <$> hGet h n
declareForeign "IO.getBytes.v3" boxNatToEFBox . mkForeignIOF $ \(h,n) -> Bytes.fromArray <$> hGet h n
declareForeign "IO.putBytes.v2" boxBoxToEFBox . mkForeignIOF $ \(h,bs) -> hPut h (Bytes.toArray bs)
declareForeign "IO.systemTime.v2" unitToEFNat
declareForeign "IO.putBytes.v3" boxBoxToEFBox . mkForeignIOF $ \(h,bs) -> hPut h (Bytes.toArray bs)
declareForeign "IO.systemTime.v3" unitToEFNat
$ mkForeignIOF $ \() -> getPOSIXTime
declareForeign "IO.getTempDirectory.v2" unitToEFBox
declareForeign "IO.getTempDirectory.v3" unitToEFBox
$ mkForeignIOF $ \() -> getTemporaryDirectory
declareForeign "IO.createTempDirectory" boxToEFBox
@ -1307,66 +1363,72 @@ declareForeigns = do
temp <- getTemporaryDirectory
createTempDirectory temp prefix
declareForeign "IO.getCurrentDirectory.v2" direct
declareForeign "IO.getCurrentDirectory.v3" direct
. mkForeignIOF $ \() -> getCurrentDirectory
declareForeign "IO.setCurrentDirectory.v2" boxToEF0
declareForeign "IO.setCurrentDirectory.v3" boxToEF0
$ mkForeignIOF setCurrentDirectory
declareForeign "IO.fileExists.v2" boxToEFBool
declareForeign "IO.fileExists.v3" boxToEFBool
$ mkForeignIOF doesPathExist
declareForeign "IO.isDirectory.v2" boxToEFBool
declareForeign "IO.isDirectory.v3" boxToEFBool
$ mkForeignIOF doesDirectoryExist
declareForeign "IO.createDirectory.v2" boxToEF0
declareForeign "IO.createDirectory.v3" boxToEF0
$ mkForeignIOF $ createDirectoryIfMissing True
declareForeign "IO.removeDirectory.v2" boxToEF0
declareForeign "IO.removeDirectory.v3" boxToEF0
$ mkForeignIOF removeDirectoryRecursive
declareForeign "IO.renameDirectory.v2" boxBoxToEF0
declareForeign "IO.renameDirectory.v3" boxBoxToEF0
$ mkForeignIOF $ uncurry renameDirectory
declareForeign "IO.removeFile.v2" boxToEF0
declareForeign "IO.removeFile.v3" boxToEF0
$ mkForeignIOF removeFile
declareForeign "IO.renameFile.v2" boxBoxToEF0
declareForeign "IO.renameFile.v3" boxBoxToEF0
$ mkForeignIOF $ uncurry renameFile
declareForeign "IO.getFileTimestamp.v2" boxToEFNat
declareForeign "IO.getFileTimestamp.v3" boxToEFNat
. mkForeignIOF $ fmap utcTimeToPOSIXSeconds . getModificationTime
declareForeign "IO.getFileSize.v2" boxToEFNat
declareForeign "IO.getFileSize.v3" boxToEFNat
-- TODO: truncating integer
. mkForeignIOF $ \fp -> fromInteger @Word64 <$> getFileSize fp
declareForeign "IO.serverSocket.v2" boxBoxToEFBox
. mkForeignIOF $ \(mhst,port) ->
declareForeign "IO.serverSocket.v3" maybeBoxToEFBox
. mkForeignIOF $ \(mhst :: Maybe Text
, port) ->
fst <$> SYS.bindSock (hostPreference mhst) port
declareForeign "IO.listen.v2" boxToEF0
. mkForeignIOF $ \sk -> SYS.listenSock sk 2048
declareForeign "IO.socketPort" boxToEFNat
. mkForeignIOF $ \(handle :: Socket) -> do
n <- SYS.socketPort handle
return (fromIntegral n :: Word64)
declareForeign "IO.clientSocket.v2" boxBoxDirect
declareForeign "IO.listen.v3" boxToEF0
. mkForeignIOF $ \sk -> SYS.listenSock sk 2
declareForeign "IO.clientSocket.v3" boxBoxToEFBox
. mkForeignIOF $ fmap fst . uncurry SYS.connectSock
declareForeign "IO.closeSocket.v2" boxToEF0
declareForeign "IO.closeSocket.v3" boxToEF0
$ mkForeignIOF SYS.closeSock
declareForeign "IO.socketAccept.v2" boxDirect
declareForeign "IO.socketAccept.v3" boxToEFBox
. mkForeignIOF $ fmap fst . SYS.accept
declareForeign "IO.socketSend.v2" boxBoxToEF0
declareForeign "IO.socketSend.v3" boxBoxToEF0
. mkForeignIOF $ \(sk,bs) -> SYS.send sk (Bytes.toArray bs)
declareForeign "IO.socketReceive.v2" boxNatToEFBox
declareForeign "IO.socketReceive.v3" boxNatToEFBox
. mkForeignIOF $ \(hs,n) ->
maybe Bytes.empty Bytes.fromArray <$> SYS.recv hs n
declareForeign "IO.kill.v2" boxTo0 $ mkForeignIOF killThread
declareForeign "IO.kill.v3" boxTo0 $ mkForeignIOF killThread
declareForeign "IO.delay.v2" natToUnit $ mkForeignIOF threadDelay
declareForeign "IO.delay.v3" natToUnit $ mkForeignIOF threadDelay
declareForeign "IO.stdHandle" standard'handle
. mkForeign $ \(n :: Int) -> case n of
@ -1381,25 +1443,25 @@ declareForeigns = do
declareForeign "MVar.newEmpty.v2" unitDirect
. mkForeign $ \() -> newEmptyMVar @Closure
declareForeign "MVar.take.v2" boxToEFBox
declareForeign "MVar.take.v3" boxToEFBox
. mkForeignIOF $ \(mv :: MVar Closure) -> takeMVar mv
declareForeign "MVar.tryTake" boxToMaybeBox
. mkForeign $ \(mv :: MVar Closure) -> tryTakeMVar mv
declareForeign "MVar.put.v2" boxBoxToEF0
declareForeign "MVar.put.v3" boxBoxToEF0
. mkForeignIOF $ \(mv :: MVar Closure, x) -> putMVar mv x
declareForeign "MVar.tryPut" boxBoxToEFBool
. mkForeign $ \(mv :: MVar Closure, x) -> tryPutMVar mv x
declareForeign "MVar.swap.v2" boxBoxToEFBox
declareForeign "MVar.swap.v3" boxBoxToEFBox
. mkForeignIOF $ \(mv :: MVar Closure, x) -> swapMVar mv x
declareForeign "MVar.isEmpty" boxToBool
. mkForeign $ \(mv :: MVar Closure) -> isEmptyMVar mv
declareForeign "MVar.read.v2" boxBoxToEFBox
declareForeign "MVar.read.v3" boxBoxToEFBox
. mkForeignIOF $ \(mv :: MVar Closure) -> readMVar mv
declareForeign "MVar.tryRead" boxToMaybeBox
@ -1408,8 +1470,55 @@ declareForeigns = do
declareForeign "Text.toUtf8" boxDirect . mkForeign
$ pure . Bytes.fromArray . encodeUtf8
declareForeign "Text.fromUtf8.v2" boxToEFBox . mkForeign
$ pure . mapLeft (Failure ioFailureReference . pack . show) . decodeUtf8' . Bytes.toArray
declareForeign "Text.fromUtf8.v3" boxToEFBox . mkForeign
$ pure . mapLeft (\t -> Failure ioFailureReference (pack ( show t)) unitValue) . decodeUtf8' . Bytes.toArray
declareForeign "Tls.ClientConfig.default" boxBoxDirect . mkForeign
$ \(hostName::Text, serverId:: Bytes.Bytes) ->
fmap (\store ->
(defaultParamsClient (unpack hostName) (Bytes.toArray serverId)) {
TLS.clientSupported = def { TLS.supportedCiphers = Cipher.ciphersuite_strong },
TLS.clientShared = def { TLS.sharedCAStore = store }
}) X.getSystemCertificateStore
declareForeign "Tls.ServerConfig.default" boxBoxDirect $ mkForeign
$ \(certs :: [X.SignedCertificate], key :: X.PrivKey) ->
pure $ (def :: TLS.ServerParams) { TLS.serverSupported = def { TLS.supportedCiphers = Cipher.ciphersuite_strong }
, TLS.serverShared = def { TLS.sharedCredentials = Credentials [((X.CertificateChain certs), key)] }
}
let updateClient :: X.CertificateStore -> TLS.ClientParams -> TLS.ClientParams
updateClient certs client = client { TLS.clientShared = ((clientShared client) { TLS.sharedCAStore = certs }) } in
declareForeign "Tls.ClientConfig.certificates.set" boxBoxDirect . mkForeign $
\(certs :: [X.SignedCertificate], params :: ClientParams) -> pure $ updateClient (X.makeCertificateStore certs) params
let updateServer :: X.CertificateStore -> TLS.ServerParams -> TLS.ServerParams
updateServer certs client = client { TLS.serverShared = ((serverShared client) { TLS.sharedCAStore = certs }) } in
declareForeign "Tls.ServerConfig.certificates.set" boxBoxDirect . mkForeign $
\(certs :: [X.SignedCertificate], params :: ServerParams) -> pure $ updateServer (X.makeCertificateStore certs) params
declareForeign "TVar.new" boxDirect . mkForeign
$ \(c :: Closure) -> unsafeSTMToIO $ STM.newTVar c
declareForeign "TVar.read" boxDirect . mkForeign
$ \(v :: STM.TVar Closure) -> unsafeSTMToIO $ STM.readTVar v
declareForeign "TVar.write" boxBoxTo0 . mkForeign
$ \(v :: STM.TVar Closure, c :: Closure)
-> unsafeSTMToIO $ STM.writeTVar v c
declareForeign "TVar.newIO" boxDirect . mkForeign
$ \(c :: Closure) -> STM.newTVarIO c
declareForeign "TVar.readIO" boxDirect . mkForeign
$ \(v :: STM.TVar Closure) -> STM.readTVarIO v
declareForeign "TVar.swap" boxBoxDirect . mkForeign
$ \(v, c :: Closure) -> unsafeSTMToIO $ STM.swapTVar v c
declareForeign "STM.retry" unitDirect . mkForeign
$ \() -> unsafeSTMToIO STM.retry :: IO Closure
let
defaultSupported :: TLS.Supported
@ -1430,6 +1539,10 @@ declareForeigns = do
\(config :: TLS.ClientParams,
socket :: SYS.Socket) -> TLS.contextNew socket config
declareForeign "Tls.newServer" boxBoxToEFBox . mkForeignTls $
\(config :: TLS.ServerParams,
socket :: SYS.Socket) -> TLS.contextNew socket config
declareForeign "Tls.handshake" boxToEFBox . mkForeignTls $
\(tls :: TLS.Context) -> TLS.handshake tls
@ -1437,6 +1550,24 @@ declareForeigns = do
\(tls :: TLS.Context,
bytes :: Bytes.Bytes) -> TLS.sendData tls (Bytes.toLazyByteString bytes)
let wrapFailure t = Failure tlsFailureReference (pack t) unitValue
decoded :: Bytes.Bytes -> Either String PEM
decoded bytes = fmap head $ pemParseLBS $ Bytes.toLazyByteString bytes
asCert :: PEM -> Either String X.SignedCertificate
asCert pem = X.decodeSignedCertificate $ pemContent pem
in
declareForeign "Tls.decodeCert" boxToEFBox . mkForeign $
\(bytes :: Bytes.Bytes) -> pure $ mapLeft wrapFailure $ (decoded >=> asCert) bytes
declareForeign "Tls.encodeCert" boxDirect . mkForeign $
\(cert :: X.SignedCertificate) -> pure $ Bytes.fromArray $ X.encodeSignedObject cert
declareForeign "Tls.decodePrivateKey" boxDirect . mkForeign $
\(bytes :: Bytes.Bytes) -> pure $ X.readKeyFileFromMemory $ L.toStrict $ Bytes.toLazyByteString bytes
declareForeign "Tls.encodePrivateKey" boxDirect . mkForeign $
\(privateKey :: X.PrivKey) -> pure $ pack $ show privateKey
declareForeign "Tls.receive" boxToEFBox . mkForeignTls $
\(tls :: TLS.Context) -> do
bs <- TLS.recvData tls
@ -1564,3 +1695,6 @@ builtinTypeBackref = mapFromList $ swap <$> typeReferences
builtinForeigns :: EnumMap Word64 ForeignFunc
builtinForeigns | (_, _, m) <- foreignDeclResults @Symbol = m
unsafeSTMToIO :: STM.STM a -> IO a
unsafeSTMToIO (STM.STM m) = IO m

View File

@ -21,6 +21,7 @@ import Data.Text (Text, unpack)
import Data.Tagged (Tagged(..))
import Network.Socket (Socket)
import qualified Network.TLS as TLS (ClientParams, Context, ServerParams)
import qualified Data.X509 as X509
import System.IO (Handle)
import Unison.Util.Bytes (Bytes)
import Unison.Reference (Reference)
@ -91,6 +92,8 @@ instance BuiltinForeign Socket where foreignRef = Tagged Ty.socketRef
instance BuiltinForeign ThreadId where foreignRef = Tagged Ty.threadIdRef
instance BuiltinForeign TLS.ClientParams where foreignRef = Tagged Ty.tlsClientConfigRef
instance BuiltinForeign TLS.ServerParams where foreignRef = Tagged Ty.tlsServerConfigRef
instance BuiltinForeign X509.SignedCertificate where foreignRef = Tagged Ty.tlsSignedCertRef
instance BuiltinForeign X509.PrivKey where foreignRef = Tagged Ty.tlsPrivateKeyRef
instance BuiltinForeign FilePath where foreignRef = Tagged Ty.filePathRef
instance BuiltinForeign TLS.Context where foreignRef = Tagged Ty.tlsRef
instance BuiltinForeign (SuperGroup Symbol) where
@ -103,7 +106,7 @@ data HashAlgorithm where
newtype Tls = Tls TLS.Context
data Failure = Failure Reference Text -- todo: Failure a = Failure Reference Text (Any a)
data Failure a = Failure Reference Text a
instance BuiltinForeign HashAlgorithm where foreignRef = Tagged Ty.hashAlgorithmRef

View File

@ -15,6 +15,7 @@ import GHC.IO.Exception (IOException(..), IOErrorType(..))
import Control.Concurrent (ThreadId)
import Control.Concurrent.MVar (MVar)
import Control.Concurrent.STM (TVar)
import Data.Foldable (toList)
import Data.Text (Text, pack, unpack)
import Data.Time.Clock.POSIX (POSIXTime)
@ -25,7 +26,7 @@ import System.IO (BufferMode(..), SeekMode, Handle, IOMode)
import Unison.Util.Bytes (Bytes)
import Unison.Reference (Reference)
import Unison.Type (mvarRef, typeLinkRef)
import Unison.Type (mvarRef, tvarRef, typeLinkRef)
import Unison.Symbol (Symbol)
import Unison.Runtime.ANF (SuperGroup, Mem(..), Value)
@ -257,13 +258,15 @@ instance (ForeignConvention a, ForeignConvention b)
(ustk, bstk) <- writeForeign ustk bstk y
writeForeign ustk bstk x
instance ForeignConvention Failure where
instance ForeignConvention a => ForeignConvention (Failure a) where
readForeign us bs ustk bstk = do
(us,bs,typeref) <- readTypelink us bs ustk bstk
(us,bs,message) <- readForeign us bs ustk bstk
pure (us, bs, (Failure typeref message))
(us,bs,any) <- readForeign us bs ustk bstk
pure (us, bs, Failure typeref message any)
writeForeign ustk bstk (Failure typeref message) = do
writeForeign ustk bstk (Failure typeref message any) = do
(ustk, bstk) <- writeForeign ustk bstk any
(ustk, bstk) <- writeForeign ustk bstk message
writeTypeLink ustk bstk typeref
@ -320,6 +323,10 @@ instance ForeignConvention (MVar Closure) where
readForeign = readForeignAs (unwrapForeign . marshalToForeign)
writeForeign = writeForeignAs (Foreign . Wrap mvarRef)
instance ForeignConvention (TVar Closure) where
readForeign = readForeignAs (unwrapForeign . marshalToForeign)
writeForeign = writeForeignAs (Foreign . Wrap tvarRef)
instance ForeignConvention (SuperGroup Symbol) where
readForeign = readForeignBuiltin
writeForeign = writeForeignBuiltin

View File

@ -27,8 +27,12 @@ import qualified Unison.UnisonFile as UF
import qualified Unison.Var as Var
import qualified Unison.Names3 as Names
debug :: Bool
debug = False
typecheckedFile :: UF.TypecheckedUnisonFile Symbol Ann
typecheckedFile = typecheckedFile'
typecheckedFile = let x = typecheckedFile' in
if debug then trace ("IOSource.typecheckedFile = " ++ show x) x else x
typecheckedFile' :: forall v. Var.Var v => UF.TypecheckedUnisonFile v Ann
typecheckedFile' = let

View File

@ -417,7 +417,13 @@ data Instr
-- Put a delimiter on the continuation
| Reset !(EnumSet Word64) -- prompt ids
-- Fork thread evaluating delayed computation on boxed stack
| Fork !Int
-- Atomic transaction evaluating delayed computation on boxed stack
| Atomically !Int
-- Build a sequence consisting of a variable number of arguments
| Seq !Args
deriving (Show, Eq, Ord)
@ -1059,6 +1065,9 @@ emitPOp ANF.BLDS = Seq
emitPOp ANF.FORK = \case
BArg1 i -> Fork i
_ -> error "fork takes exactly one boxed argument"
emitPOp ANF.ATOM = \case
BArg1 i -> Atomically i
_ -> error "atomically takes exactly one boxed argument"
emitPOp ANF.PRNT = \case
BArg1 i -> Print i
_ -> error "print takes exactly one boxed argument"

View File

@ -10,6 +10,7 @@ module Unison.Runtime.Machine where
import GHC.Stack
import Control.Concurrent.STM as STM
import GHC.Conc as STM (unsafeIOToSTM)
import Data.Maybe (fromMaybe)
@ -26,7 +27,7 @@ import qualified Data.Set as S
import Control.Exception
import Control.Lens ((<&>))
import Control.Concurrent (forkIOWithUnmask, ThreadId)
import Control.Concurrent (forkIO, ThreadId)
import qualified Data.Primitive.PrimArray as PA
@ -47,9 +48,6 @@ import Unison.Runtime.Stack
import Unison.Runtime.MCode
import qualified Unison.Type as Rf
import qualified Unison.Runtime.IOSource as Rf
import qualified Unison.Util.Pretty as Pr
import qualified Unison.Util.Bytes as By
import Unison.Util.EnumContainers as EC
@ -302,7 +300,12 @@ exec !env !denv !ustk !bstk !k (ForeignCall _ w args)
exec !env !denv !ustk !bstk !k (Fork i) = do
tid <- forkEval env =<< peekOff bstk i
bstk <- bump bstk
poke bstk . Foreign . Wrap Rf.threadIdReference $ tid
poke bstk . Foreign . Wrap Rf.threadIdRef $ tid
pure (denv, ustk, bstk, k)
exec !env !denv !ustk !bstk !k (Atomically i) = do
c <- peekOff bstk i
bstk <- bump bstk
atomicEval env (poke bstk) c
pure (denv, ustk, bstk, k)
{-# inline exec #-}
@ -343,19 +346,20 @@ eval !_ !_ !_ !_ !_ (Die s) = die s
forkEval :: CCache -> Closure -> IO ThreadId
forkEval env clo
= forkIOWithUnmask $ \unmask ->
unmask (apply1 err env clo) `catch` \case
PE e -> putStrLn "runtime exception"
>> print (Pr.render 70 e)
BU _ -> putStrLn $ "unison exception reached top level"
= forkIO (apply1 err env clo)
where
err :: Stack 'UN -> Stack 'BX -> IO ()
err _ bstk = peek bstk >>= \case
-- Left e
DataB1 _ 0 e -> throwIO $ BU e
_ -> pure ()
err _ _ = pure ()
{-# inline forkEval #-}
atomicEval :: CCache -> (Closure -> IO ()) -> Closure -> IO ()
atomicEval env write clo
= atomically . unsafeIOToSTM $ apply1 readBack env clo
where
readBack :: Stack 'UN -> Stack 'BX -> IO ()
readBack _ bstk = peek bstk >>= write
{-# inline atomicEval #-}
-- fast path application
enter
:: CCache -> DEnv -> Stack 'UN -> Stack 'BX -> K

View File

@ -145,6 +145,10 @@ decomposePattern
:: Var v
=> Reference -> Int -> Int -> P.Pattern v
-> [[P.Pattern v]]
decomposePattern rf0 t _ (P.Boolean _ b)
| rf0 == Rf.booleanRef
, t == if b then 1 else 0
= [[]]
decomposePattern rf0 t nfields p@(P.Constructor _ rf u ps)
| t == u
, rf0 == rf

View File

@ -153,7 +153,7 @@ resultTest rt uf filepath = do
tm' = Term.letRec' False bindings watchResult
-- note . show $ tm'
-- note . show $ Term.amap (const ()) tm
expect $ tm' == Term.amap (const ()) tm
expectEqual tm' (Term.amap (const ()) tm)
Left e -> crash $ show e
else pure ()

View File

@ -225,6 +225,7 @@ library
network,
network-simple,
nonempty-containers,
pem,
process,
primitive,
random,
@ -249,6 +250,8 @@ library
util,
vector,
unicode-show,
x509,
x509-store,
x509-system,
-- v2
unison-core,

View File

@ -41,6 +41,12 @@ extra-deps:
- sqlite-simple-0.4.18.0@sha256:3ceea56375c0a3590c814e411a4eb86943f8d31b93b110ca159c90689b6b39e5,3002
- direct-sqlite-2.3.26@sha256:04e835402f1508abca383182023e4e2b9b86297b8533afbd4e57d1a5652e0c23,3718
- ListLike-4.7.3
# remove these when stackage upgrades containers
- containers-0.6.4.1
- text-1.2.4.1
- binary-0.8.8.0
- parsec-3.1.14.0
- Cabal-3.2.1.0
ghc-options:
# All packages

View File

@ -227,20 +227,30 @@ filePathRef = Reference.Builtin "FilePath"
threadIdRef = Reference.Builtin "ThreadId"
socketRef = Reference.Builtin "Socket"
mvarRef :: Reference
mvarRef, tvarRef :: Reference
mvarRef = Reference.Builtin "MVar"
tvarRef = Reference.Builtin "TVar"
tlsRef :: Reference
tlsRef = Reference.Builtin "Tls"
stmRef :: Reference
stmRef = Reference.Builtin "STM"
tlsClientConfigRef :: Reference
tlsClientConfigRef = Reference.Builtin "Tls.ClientConfig"
tlsServerConfigRef :: Reference
tlsServerConfigRef = Reference.Builtin "Tls.ServerConfig"
tlsCiphersRef :: Reference
tlsCiphersRef = Reference.Builtin "Tls.Ciphers"
tlsSignedCertRef :: Reference
tlsSignedCertRef = Reference.Builtin "Tls.SignedCert"
tlsPrivateKeyRef :: Reference
tlsPrivateKeyRef = Reference.Builtin "Tls.PrivateKey"
tlsCipherRef :: Reference
tlsCipherRef = Reference.Builtin "Tls.Cipher"
tlsVersionRef :: Reference
tlsVersionRef = Reference.Builtin "Tls.Version"
@ -255,6 +265,9 @@ valueRef = Reference.Builtin "Value"
anyRef :: Reference
anyRef = Reference.Builtin "Any"
any :: Ord v => a -> Type v a
any a = ref a anyRef
builtin :: Ord v => a -> Text -> Type v a
builtin a = ref a . Reference.Builtin

View File

@ -72,7 +72,7 @@ autoCleaned.handler _ =
Left _ -> handle k dir with go dirs
{ TempDirs.removeDir dir -> k } ->
handle k (removeDirectory dir) with go (filter (d -> not (d == dir)) dirs)
handle k (removeDirectory dir) with go (filter (d -> not (d == dir)) dirs)
go []
@ -134,7 +134,7 @@ evalTest a = handle
runTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t)) -> results :+ (Fail t)
(results, Left (Failure _ t _)) -> results :+ (Fail t)
```
@ -157,13 +157,13 @@ testAutoClean _ =
dir
match evalTest go with
(results, Left (Failure _ t)) -> results :+ (Fail t)
(results, Left (Failure _ t _)) -> results :+ (Fail t)
(results, Right dir) ->
match isDirectory dir with
Right b -> if b
then results :+ (Fail "our temporary directory should no longer exist")
else results :+ (Ok "our temporary directory should no longer exist")
Left (Failure _ t) -> results :+ (Fail t)
Left (Failure _ t _) -> results :+ (Fail t)
```
```ucm
@ -249,18 +249,19 @@ testSeek : '{io2.IO} [Result]
testSeek _ =
test = 'let
tempDir = toException (newTempDir "seek")
emit (Ok "seeked")
fooFile = tempDir ++ "/foo"
handle1 = toException (openFile fooFile FileMode.Write)
handle1 = toException (openFile fooFile FileMode.Append)
putBytes handle1 (toUtf8 "12345678")
closeFile handle1
handle3 = toException (openFile fooFile FileMode.Read)
check "readable file should be seekable" (toException (isSeekable handle3))
check "shouldn't be the EOF" (not (toException (isFileEOF handle3)))
expectU "we should be at position 0" +0 (toException (handlePosition handle3))
expectU "we should be at position 0" 0 (toException (handlePosition handle3))
toException (seekHandle handle3 AbsoluteSeek +1)
expectU "we should be at position 1" +1 (toException (handlePosition handle3))
expectU "we should be at position 1" 1 (toException (handlePosition handle3))
bytes3a = toException (getBytes handle3 1000)
text3a = toException (Text.fromUtf8 bytes3a)
expectU "should be able to read our temporary file after seeking" "2345678" text3a
@ -272,25 +273,24 @@ testAppend : '{io2.IO} [Result]
testAppend _ =
test = 'let
tempDir = toException (newTempDir "openFile")
fooFile = tempDir ++ "/foo"
handle1 = toException (openFile fooFile FileMode.Write)
putBytes handle1 (toUtf8 "test1")
closeFile handle1
toException (putBytes handle1 (toUtf8 "test1"))
toException (closeFile handle1)
handle2 = toException (openFile fooFile FileMode.Append)
putBytes handle2 (toUtf8 "test2")
expectU "we should be at position 4" +4 (toException (handlePosition handle2))
check "which is the EOF" (toException (isFileEOF handle2))
closeFile handle2
toException (putBytes handle2 (toUtf8 "test2"))
toException (closeFile handle2)
handle3 = toException (openFile fooFile FileMode.Read)
bytes3 = toException (getBytes handle3 1000)
text3 = toException (Text.fromUtf8 bytes3)
expectU "should be able to read our temporary file" "test1test2" text3
closeFile handle3
runTest test
```
```ucm
@ -305,7 +305,7 @@ testSystemTime : '{io2.IO} [Result]
testSystemTime _ =
test = 'let
t = toException !io2.IO.systemTime
check "systemTime should be sane" ((t > 1600000000) && (t > 2000000000))
check "systemTime should be sane" ((t > 1600000000) && (t < 2000000000))
runTest test
```
@ -321,34 +321,3 @@ contain a signle typed value. They are a building block on which many
concurrency primitives can be built that allow multiple threads to
synchronize and share data.
```unison
testMvars: '{io2.IO}[Result]
testMvars _ =
test = 'let
test = "test"
test2 = "test2"
ma = MVar.new test
check "ma should not be empty" (not (isEmpty ma))
test' = toException (take ma)
expectU "should reap what you sow" test test'
check "ma should be empty" (isEmpty ma)
toException (put ma test)
test'' = toException (swap ma test2)
expectU "swap returns old contents" test test''
test''' = toException (swap ma test)
expectU "swap returns old contents" test2 test'''
ma2 = !MVar.newEmpty
check "tryTake should succeed when not empty" (not (isNone (tryTake ma)))
check "tryTake should not succeed when empty" (isNone (tryTake ma))
check "ma2 should be empty" (isEmpty ma2)
check "tryTake should fail when empty" (isNone (tryTake ma2))
runTest test
```
```ucm
.> add
.> io.test testMvars
```

View File

@ -67,7 +67,7 @@ autoCleaned.handler _ =
Left _ -> handle k dir with go dirs
{ TempDirs.removeDir dir -> k } ->
handle k (removeDirectory dir) with go (filter (d -> not (d == dir)) dirs)
handle k (removeDirectory dir) with go (filter (d -> not (d == dir)) dirs)
go []
@ -129,7 +129,7 @@ evalTest a = handle
runTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t)) -> results :+ (Fail t)
(results, Left (Failure _ t _)) -> results :+ (Fail t)
```
@ -148,13 +148,13 @@ testAutoClean _ =
dir
match evalTest go with
(results, Left (Failure _ t)) -> results :+ (Fail t)
(results, Left (Failure _ t _)) -> results :+ (Fail t)
(results, Right dir) ->
match isDirectory dir with
Right b -> if b
then results :+ (Fail "our temporary directory should no longer exist")
else results :+ (Ok "our temporary directory should no longer exist")
Left (Failure _ t) -> results :+ (Fail t)
Left (Failure _ t _) -> results :+ (Fail t)
```
```ucm
@ -322,18 +322,19 @@ testSeek : '{io2.IO} [Result]
testSeek _ =
test = 'let
tempDir = toException (newTempDir "seek")
emit (Ok "seeked")
fooFile = tempDir ++ "/foo"
handle1 = toException (openFile fooFile FileMode.Write)
handle1 = toException (openFile fooFile FileMode.Append)
putBytes handle1 (toUtf8 "12345678")
closeFile handle1
handle3 = toException (openFile fooFile FileMode.Read)
check "readable file should be seekable" (toException (isSeekable handle3))
check "shouldn't be the EOF" (not (toException (isFileEOF handle3)))
expectU "we should be at position 0" +0 (toException (handlePosition handle3))
expectU "we should be at position 0" 0 (toException (handlePosition handle3))
toException (seekHandle handle3 AbsoluteSeek +1)
expectU "we should be at position 1" +1 (toException (handlePosition handle3))
expectU "we should be at position 1" 1 (toException (handlePosition handle3))
bytes3a = toException (getBytes handle3 1000)
text3a = toException (Text.fromUtf8 bytes3a)
expectU "should be able to read our temporary file after seeking" "2345678" text3a
@ -345,25 +346,24 @@ testAppend : '{io2.IO} [Result]
testAppend _ =
test = 'let
tempDir = toException (newTempDir "openFile")
fooFile = tempDir ++ "/foo"
handle1 = toException (openFile fooFile FileMode.Write)
putBytes handle1 (toUtf8 "test1")
closeFile handle1
toException (putBytes handle1 (toUtf8 "test1"))
toException (closeFile handle1)
handle2 = toException (openFile fooFile FileMode.Append)
putBytes handle2 (toUtf8 "test2")
expectU "we should be at position 4" +4 (toException (handlePosition handle2))
check "which is the EOF" (toException (isFileEOF handle2))
closeFile handle2
toException (putBytes handle2 (toUtf8 "test2"))
toException (closeFile handle2)
handle3 = toException (openFile fooFile FileMode.Read)
bytes3 = toException (getBytes handle3 1000)
text3 = toException (Text.fromUtf8 bytes3)
expectU "should be able to read our temporary file" "test1test2" text3
closeFile handle3
runTest test
```
@ -391,10 +391,14 @@ testAppend _ =
New test results:
◉ testSeek seeked
◉ testSeek readable file should be seekable
◉ testSeek shouldn't be the EOF
◉ testSeek we should be at position 0
◉ testSeek we should be at position 1
◉ testSeek should be able to read our temporary file after seeking
✅ 2 test(s) passing
6 test(s) passing
Tip: Use view testSeek to view the source of a test.
@ -402,7 +406,11 @@ testAppend _ =
New test results:
😶 No tests available.
◉ testAppend should be able to read our temporary file
✅ 1 test(s) passing
Tip: Use view testAppend to view the source of a test.
```
### SystemTime
@ -411,7 +419,7 @@ testSystemTime : '{io2.IO} [Result]
testSystemTime _ =
test = 'let
t = toException !io2.IO.systemTime
check "systemTime should be sane" ((t > 1600000000) && (t > 2000000000))
check "systemTime should be sane" ((t > 1600000000) && (t < 2000000000))
runTest test
```
@ -452,68 +460,3 @@ contain a signle typed value. They are a building block on which many
concurrency primitives can be built that allow multiple threads to
synchronize and share data.
```unison
testMvars: '{io2.IO}[Result]
testMvars _ =
test = 'let
test = "test"
test2 = "test2"
ma = MVar.new test
check "ma should not be empty" (not (isEmpty ma))
test' = toException (take ma)
expectU "should reap what you sow" test test'
check "ma should be empty" (isEmpty ma)
toException (put ma test)
test'' = toException (swap ma test2)
expectU "swap returns old contents" test test''
test''' = toException (swap ma test)
expectU "swap returns old contents" test2 test'''
ma2 = !MVar.newEmpty
check "tryTake should succeed when not empty" (not (isNone (tryTake ma)))
check "tryTake should not succeed when empty" (isNone (tryTake ma))
check "ma2 should be empty" (isEmpty ma2)
check "tryTake should fail when empty" (isNone (tryTake ma2))
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
testMvars : '{io2.IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
testMvars : '{io2.IO} [Result]
.> io.test testMvars
New test results:
◉ testMvars ma should not be empty
◉ testMvars should reap what you sow
◉ testMvars ma should be empty
◉ testMvars swap returns old contents
◉ testMvars swap returns old contents
◉ testMvars tryTake should succeed when not empty
◉ testMvars tryTake should not succeed when empty
◉ testMvars ma2 should be empty
◉ testMvars tryTake should fail when empty
✅ 9 test(s) passing
Tip: Use view testMvars to view the source of a test.
```

View File

@ -0,0 +1,142 @@
# tests for io2.MVar
```ucm:hide
.> builtins.merge
.> builtins.mergeio
.> cd builtin
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```unison:hide
use .builtin.io2 Failure
filter: (a -> Boolean) -> [a] -> [a]
filter f all =
go acc = cases
a +: as -> if (f a) then go (cons a acc) as else go acc as
[] -> acc
go [] all
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
stdout = IO.stdHandle StdErr
printText : Text -> {io2.IO} Either Failure ()
printText t = putBytes stdout (toUtf8 t)
expect : Text -> (a -> a -> Boolean) -> a -> a -> {Stream Result} ()
expect msg compare expected actual = if compare expected actual then emit (Ok msg) else emit (Fail msg)
expectU : Text -> a -> a -> {Stream Result} ()
expectU msg expected actual = expect msg (==) expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> ([Result], Either Failure a)
evalTest a = handle
(handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler)
with !autoCleaned.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO, TempDirs} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t)) -> results :+ (Fail t)
```
```ucm:hide
.> add
```
`MVar`s are mutable, sharable storage for a single value, which may or
may not be present at any given time. It is sharable in the sense that
it is safe for multiple threads to attempt simultaneous reading and
writing to and from the same MVar safely.
MVars are the building block on which many other concurrency
primitives can be built, such as Futures, Run at most once initializer
blocks, Queues, etc.
```unison
testMvars: '{io2.IO}[Result]
testMvars _ =
test = 'let
test = "test"
test2 = "test2"
ma = MVar.new test
check "ma should not be empty" (not (isEmpty ma))
test' = toException (take ma)
expectU "should reap what you sow" test test'
check "ma should be empty" (isEmpty ma)
toException (put ma test)
test'' = toException (swap ma test2)
expectU "swap returns old contents" test test''
test''' = toException (swap ma test)
expectU "swap returns old contents" test2 test'''
ma2 = !MVar.newEmpty
check "tryTake should succeed when not empty" (not (isNone (tryTake ma)))
check "tryTake should not succeed when empty" (isNone (tryTake ma))
check "ma2 should be empty" (isEmpty ma2)
check "tryTake should fail when empty" (isNone (tryTake ma2))
runTest test
```
```ucm
.> add
.> io.test testMvars
```

View File

@ -0,0 +1,234 @@
# Tests for network related builtins
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```ucm:hide
.> builtins.merge
.> builtins.mergeio
.> cd builtin
```
```unison:hide
use .builtin.io2 Failure
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
```
```ucm:hide
.> add
```
### Creating server sockets
This section tests functions in the IO builtin related to binding to
TCP server socket, as to be able to accept incoming TCP connections.
```
builtin.io2.IO.serverSocket : Optional Text -> Text ->{io2.IO} Either Failure io2.Socket
```
This function takes two parameters, The first is the Hostname. If None
is provided, We will attempt to bind to 0.0.0.0 (All ipv4
addresses). We currently only support IPV4 (we should fix this!)
The second is the name of the port to bind to. This can be
a decimal representation of a port number between 1-65535. This can be
a named port like "ssh" (for port 22) or "kermit" (for port 1649),
This mapping of names to port numbers is maintained by the [nsswitch
service](https://en.wikipedia.org/wiki/Name_Service_Switch), typically
stored in `/etc/services` and queried with the `getent` tool:
# map number to name
$ getent services 22
ssh 22/tcp
# map name to number
$ getent services finger
finger 79/tcp
# get a list of all known names
$ getent services | head
tcpmux 1/tcp
echo 7/tcp
echo 7/udp
discard 9/tcp sink null
discard 9/udp sink null
systat 11/tcp users
daytime 13/tcp
daytime 13/udp
netstat 15/tcp
qotd 17/tcp quote
Below shows different examples of how we might specify the server coordinates.
``` unison
testExplicitHost : '{io2.IO} [Result]
testExplicitHost _ =
test = 'let
sock = toException (io2.IO.serverSocket (Some "127.0.0.1") "1028")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU Nat.toText "should have bound to port 1028" 1028 port
runTest test
testDefaultHost : '{io2.IO} [Result]
testDefaultHost _ =
test = 'let
sock = toException (io2.IO.serverSocket None "1028")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU Nat.toText "should have bound to port 1028" 1028 port
runTest test
testDefaultPort : '{io2.IO} [Result]
testDefaultPort _ =
test = 'let
sock = toException (io2.IO.serverSocket None "0")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
check "port should be > 1024" (1024 < port)
check "port should be < 65536" (65536 > port)
runTest test
```
```ucm
.> add
.> io.test testDefaultPort
```
This example demonstrates connecting a TCP client socket to a TCP server socket. A thread is started for both client and server. The server socket asks for any availalbe port (by passing "0" as the port number). The server thread then queries for the actual assigned port number, and puts that into an MVar which the client thread can read. The client thread then reads a string from the server and reports it back to the main thread via a different MVar.
```unison
serverThread: MVar Nat -> Text -> '{io2.IO}()
serverThread portVar toSend = 'let
go = 'let
sock = toException (serverSocket (Some "127.0.0.1") "0")
port = toException (socketPort sock)
toException (put portVar port)
toException (listen sock)
sock' = toException (socketAccept sock)
toException (socketSend sock' (toUtf8 toSend))
toException (closeSocket sock')
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
clientThread : MVar Nat -> MVar Text -> '{io2.IO}()
clientThread portVar resultVar = 'let
go : '{io2.IO, Exception Failure}()
go = 'let
port = toException (take portVar)
sock = toException (clientSocket "127.0.0.1" (Nat.toText port))
msg = toException (fromUtf8 (toException (socketReceive sock 100)))
toException (MVar.put resultVar msg)
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testTcpConnect : '{io2.IO}[Result]
testTcpConnect = 'let
test = 'let
portVar = !MVar.newEmpty
resultVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
forkComp (clientThread portVar resultVar)
received = toException (MVar.take resultVar)
expectU (a->a) "should have reaped what we've sown" toSend received
runTest test
```
```ucm
.> add
.> io.test testTcpConnect
```

View File

@ -0,0 +1,286 @@
# Tests for network related builtins
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```unison
use .builtin.io2 Failure
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
```
### Creating server sockets
This section tests functions in the IO builtin related to binding to
TCP server socket, as to be able to accept incoming TCP connections.
```builtin
.io2.IO.serverSocket : Optional Text -> Text ->{io2.IO} Either Failure io2.Socket
```
This function takes two parameters, The first is the Hostname. If None
is provided, We will attempt to bind to 0.0.0.0 (All ipv4
addresses). We currently only support IPV4 (we should fix this!)
The second is the name of the port to bind to. This can be
a decimal representation of a port number between 1-65535. This can be
a named port like "ssh" (for port 22) or "kermit" (for port 1649),
This mapping of names to port numbers is maintained by the [nsswitch
service](https://en.wikipedia.org/wiki/Name_Service_Switch), typically
stored in `/etc/services` and queried with the `getent` tool:
# map number to name
$ getent services 22
ssh 22/tcp
# map name to number
$ getent services finger
finger 79/tcp
# get a list of all known names
$ getent services | head
tcpmux 1/tcp
echo 7/tcp
echo 7/udp
discard 9/tcp sink null
discard 9/udp sink null
systat 11/tcp users
daytime 13/tcp
daytime 13/udp
netstat 15/tcp
qotd 17/tcp quote
Below shows different examples of how we might specify the server coordinates.
```unison
testExplicitHost : '{io2.IO} [Result]
testExplicitHost _ =
test = 'let
sock = toException (io2.IO.serverSocket (Some "127.0.0.1") "1028")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU Nat.toText "should have bound to port 1028" 1028 port
runTest test
testDefaultHost : '{io2.IO} [Result]
testDefaultHost _ =
test = 'let
sock = toException (io2.IO.serverSocket None "1028")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU Nat.toText "should have bound to port 1028" 1028 port
runTest test
testDefaultPort : '{io2.IO} [Result]
testDefaultPort _ =
test = 'let
sock = toException (io2.IO.serverSocket None "0")
emit (Ok "successfully created socket")
port = toException (socketPort sock)
putBytes (stdHandle StdOut) (toUtf8 (toText port))
check "port should be > 1024" (1024 < port)
check "port should be < 65536" (65536 > port)
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
testDefaultHost : '{io2.IO} [Result]
testDefaultPort : '{io2.IO} [Result]
testExplicitHost : '{io2.IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
testDefaultHost : '{io2.IO} [Result]
testDefaultPort : '{io2.IO} [Result]
testExplicitHost : '{io2.IO} [Result]
.> io.test testDefaultPort
New test results:
◉ testDefaultPort successfully created socket
◉ testDefaultPort port should be > 1024
◉ testDefaultPort port should be < 65536
✅ 3 test(s) passing
Tip: Use view testDefaultPort to view the source of a test.
```
This example demonstrates connecting a TCP client socket to a TCP server socket. A thread is started for both client and server. The server socket asks for any availalbe port (by passing "0" as the port number). The server thread then queries for the actual assigned port number, and puts that into an MVar which the client thread can read. The client thread then reads a string from the server and reports it back to the main thread via a different MVar.
```unison
serverThread: MVar Nat -> Text -> '{io2.IO}()
serverThread portVar toSend = 'let
go = 'let
sock = toException (serverSocket (Some "127.0.0.1") "0")
port = toException (socketPort sock)
toException (put portVar port)
toException (listen sock)
sock' = toException (socketAccept sock)
toException (socketSend sock' (toUtf8 toSend))
toException (closeSocket sock')
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
clientThread : MVar Nat -> MVar Text -> '{io2.IO}()
clientThread portVar resultVar = 'let
go : '{io2.IO, Exception Failure}()
go = 'let
port = toException (take portVar)
sock = toException (clientSocket "127.0.0.1" (Nat.toText port))
msg = toException (fromUtf8 (toException (socketReceive sock 100)))
toException (MVar.put resultVar msg)
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testTcpConnect : '{io2.IO}[Result]
testTcpConnect = 'let
test = 'let
portVar = !MVar.newEmpty
resultVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
forkComp (clientThread portVar resultVar)
received = toException (MVar.take resultVar)
expectU (a->a) "should have reaped what we've sown" toSend received
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
clientThread : MVar Nat -> MVar Text -> '{io2.IO} ()
serverThread : MVar Nat -> Text -> '{io2.IO} ()
testTcpConnect : '{io2.IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
clientThread : MVar Nat -> MVar Text -> '{io2.IO} ()
serverThread : MVar Nat -> Text -> '{io2.IO} ()
testTcpConnect : '{io2.IO} [Result]
.> io.test testTcpConnect
New test results:
◉ testTcpConnect should have reaped what we've sown
✅ 1 test(s) passing
Tip: Use view testTcpConnect to view the source of a test.
```

View File

@ -0,0 +1,104 @@
```ucm
.> builtins.merge
```
Standard helpful definitions
```unison
use io2
stdout : Handle
stdout = stdHandle StdOut
putLn : Text ->{IO} ()
putLn t =
putBytes stdout (toUtf8 (t ++ "\n"))
()
map : (a ->{e} b) -> [a] ->{e} [b]
map f l = let
go acc = cases
[] -> acc
x +: xs -> go (acc :+ f x) xs
go [] l
```
```ucm
.> add
```
Loops that access a shared counter variable, accessed in transactions.
Some thread delaying is just accomplished by counting in a loop.
```unison
use io2
count : Nat -> ()
count = cases
0 -> ()
n -> count (drop n 1)
inc : TVar Nat ->{IO} Nat
inc v =
atomically 'let
x = TVar.read v
TVar.write v (x+1)
x
loop : '{IO} Nat -> Nat -> Nat ->{IO} Nat
loop grab acc = cases
0 -> acc
n ->
m = !grab
count (m*10)
loop grab (acc+m) (drop n 1)
body : Nat -> TVar (Optional Nat) -> TVar Nat ->{IO} ()
body k out v =
n = loop '(inc v) 0 k
atomically '(TVar.write out (Some n))
```
```ucm
.> add
```
Test case.
```unison
spawn : Nat ->{IO} Result
spawn k = let
out1 = TVar.newIO None
out2 = TVar.newIO None
counter = atomically '(TVar.new 0)
forkComp '(Right (body k out1 counter))
forkComp '(Right (body k out2 counter))
p = atomically 'let
r1 = TVar.read out1
r2 = TVar.swap out2 None
match (r1, r2) with
(Some m, Some n) -> (m, n)
_ -> !STM.retry
max = TVar.readIO counter
match p with (m, n) ->
sum : Nat
sum = max * drop max 1 / 2
if m+n == sum
then Ok "verified"
else Fail (display m n sum)
display : Nat -> Nat -> Nat -> Text
display m n s =
"mismatch: " ++ toText m ++ " + " ++ toText n ++ " /= " ++ toText s
nats : [Nat]
nats = [89,100,116,144,169,188,200,233,256,300]
tests : '{IO} [Result]
tests = '(map spawn nats)
```
```ucm
.> add
.> io.test tests
```

View File

@ -0,0 +1,186 @@
```ucm
.> builtins.merge
Done.
```
Standard helpful definitions
```unison
use io2
stdout : Handle
stdout = stdHandle StdOut
putLn : Text ->{IO} ()
putLn t =
putBytes stdout (toUtf8 (t ++ "\n"))
()
map : (a ->{e} b) -> [a] ->{e} [b]
map f l = let
go acc = cases
[] -> acc
x +: xs -> go (acc :+ f x) xs
go [] l
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
map : (a ->{e} b) -> [a] ->{e} [b]
putLn : Text ->{IO} ()
stdout : Handle
```
```ucm
.> add
⍟ I've added these definitions:
map : (a ->{e} b) -> [a] ->{e} [b]
putLn : Text ->{IO} ()
stdout : Handle
```
Loops that access a shared counter variable, accessed in transactions.
Some thread delaying is just accomplished by counting in a loop.
```unison
use io2
count : Nat -> ()
count = cases
0 -> ()
n -> count (drop n 1)
inc : TVar Nat ->{IO} Nat
inc v =
atomically 'let
x = TVar.read v
TVar.write v (x+1)
x
loop : '{IO} Nat -> Nat -> Nat ->{IO} Nat
loop grab acc = cases
0 -> acc
n ->
m = !grab
count (m*10)
loop grab (acc+m) (drop n 1)
body : Nat -> TVar (Optional Nat) -> TVar Nat ->{IO} ()
body k out v =
n = loop '(inc v) 0 k
atomically '(TVar.write out (Some n))
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
body : Nat -> TVar (Optional Nat) -> TVar Nat ->{IO} ()
count : Nat -> ()
inc : TVar Nat ->{IO} Nat
loop : '{IO} Nat ->{IO} Nat ->{IO} Nat ->{IO} Nat
```
```ucm
.> add
⍟ I've added these definitions:
body : Nat -> TVar (Optional Nat) -> TVar Nat ->{IO} ()
count : Nat -> ()
inc : TVar Nat ->{IO} Nat
loop : '{IO} Nat ->{IO} Nat ->{IO} Nat ->{IO} Nat
```
Test case.
```unison
spawn : Nat ->{IO} Result
spawn k = let
out1 = TVar.newIO None
out2 = TVar.newIO None
counter = atomically '(TVar.new 0)
forkComp '(Right (body k out1 counter))
forkComp '(Right (body k out2 counter))
p = atomically 'let
r1 = TVar.read out1
r2 = TVar.swap out2 None
match (r1, r2) with
(Some m, Some n) -> (m, n)
_ -> !STM.retry
max = TVar.readIO counter
match p with (m, n) ->
sum : Nat
sum = max * drop max 1 / 2
if m+n == sum
then Ok "verified"
else Fail (display m n sum)
display : Nat -> Nat -> Nat -> Text
display m n s =
"mismatch: " ++ toText m ++ " + " ++ toText n ++ " /= " ++ toText s
nats : [Nat]
nats = [89,100,116,144,169,188,200,233,256,300]
tests : '{IO} [Result]
tests = '(map spawn nats)
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
display : Nat -> Nat -> Nat -> Text
nats : [Nat]
spawn : Nat ->{IO} Result
tests : '{IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
display : Nat -> Nat -> Nat -> Text
nats : [Nat]
spawn : Nat ->{IO} Result
tests : '{IO} [Result]
.> io.test tests
New test results:
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
◉ tests verified
✅ 10 test(s) passing
Tip: Use view tests to view the source of a test.
```

View File

@ -0,0 +1,196 @@
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```ucm:hide
.> builtins.merge
.> builtins.mergeio
.> cd builtin
```
```unison:hide
use .builtin.io2 Failure
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
```
```ucm:hide
.> add
```
Lets just make sure we can start a thread
```unison
otherThread : '{io2.IO}()
otherThread = 'let
watch "I'm the other Thread" ()
testBasicFork : '{io2.IO} [Result]
testBasicFork = 'let
test = 'let
watch "I'm the parent thread" ()
threadId = io2.IO.forkComp otherThread
emit (Ok "created thread")
runTest test
```
See if we can get another thread to stuff a value into a MVar
```ucm:hide
.> add
.> io.test testBasicFork
```
```unison
thread1 : MVar Nat -> '{io2.IO}()
thread1 mv = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
x = toException (take mv)
toException (put mv (increment x))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testBasicMultiThreadMVar : '{io2.IO} [Result]
testBasicMultiThreadMVar = 'let
test: '{io2.IO, Exception Failure, Stream Result} ()
test = 'let
mv = new 10
forkComp (thread1 mv)
next = toException (take mv)
expectU Nat.toText "other thread should have incremented" 11 next
runTest test
```
```ucm
.> add
.> io.test testBasicMultiThreadMVar
```
```unison
sendingThread: Nat -> MVar Nat -> '{io2.IO}()
sendingThread toSend mv = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
toException (put mv (increment toSend))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
receivingThread: MVar Nat -> MVar Text -> '{io2.IO}()
receivingThread recv send = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
recvd = toException (take recv)
toException (put send (toText recvd))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testTwoThreads: '{io2.IO}[Result]
testTwoThreads = 'let
test = 'let
send = !MVar.newEmpty
recv = !MVar.newEmpty
forkComp (sendingThread 6 send)
forkComp (receivingThread send recv)
recvd = toException (take recv)
expectU (x->x) "" "7" recvd
runTest test
```
```ucm
.> add
.> io.test testTwoThreads
```

View File

@ -0,0 +1,250 @@
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```unison
use .builtin.io2 Failure
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
```
Lets just make sure we can start a thread
```unison
otherThread : '{io2.IO}()
otherThread = 'let
watch "I'm the other Thread" ()
testBasicFork : '{io2.IO} [Result]
testBasicFork = 'let
test = 'let
watch "I'm the parent thread" ()
threadId = io2.IO.forkComp otherThread
emit (Ok "created thread")
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
otherThread : '{io2.IO} ()
testBasicFork : '{io2.IO} [Result]
```
See if we can get another thread to stuff a value into a MVar
```unison
thread1 : MVar Nat -> '{io2.IO}()
thread1 mv = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
x = toException (take mv)
toException (put mv (increment x))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testBasicMultiThreadMVar : '{io2.IO} [Result]
testBasicMultiThreadMVar = 'let
test: '{io2.IO, Exception Failure, Stream Result} ()
test = 'let
mv = new 10
forkComp (thread1 mv)
next = toException (take mv)
expectU Nat.toText "other thread should have incremented" 11 next
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
testBasicMultiThreadMVar : '{io2.IO} [Result]
thread1 : MVar Nat -> '{io2.IO} ()
```
```ucm
.> add
⍟ I've added these definitions:
testBasicMultiThreadMVar : '{io2.IO} [Result]
thread1 : MVar Nat -> '{io2.IO} ()
.> io.test testBasicMultiThreadMVar
New test results:
◉ testBasicMultiThreadMVar other thread should have incremented
✅ 1 test(s) passing
Tip: Use view testBasicMultiThreadMVar to view the source of a
test.
```
```unison
sendingThread: Nat -> MVar Nat -> '{io2.IO}()
sendingThread toSend mv = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
toException (put mv (increment toSend))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
receivingThread: MVar Nat -> MVar Text -> '{io2.IO}()
receivingThread recv send = 'let
go : '{io2.IO, Exception Failure} ()
go = 'let
recvd = toException (take recv)
toException (put send (toText recvd))
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
testTwoThreads: '{io2.IO}[Result]
testTwoThreads = 'let
test = 'let
send = !MVar.newEmpty
recv = !MVar.newEmpty
forkComp (sendingThread 6 send)
forkComp (receivingThread send recv)
recvd = toException (take recv)
expectU (x->x) "" "7" recvd
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
receivingThread : MVar Nat -> MVar Text -> '{io2.IO} ()
sendingThread : Nat -> MVar Nat -> '{io2.IO} ()
testTwoThreads : '{io2.IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
receivingThread : MVar Nat -> MVar Text -> '{io2.IO} ()
sendingThread : Nat -> MVar Nat -> '{io2.IO} ()
testTwoThreads : '{io2.IO} [Result]
.> io.test testTwoThreads
New test results:
◉ testTwoThreads
✅ 1 test(s) passing
Tip: Use view testTwoThreads to view the source of a test.
```

View File

@ -0,0 +1,287 @@
# Tests for TLS builtins
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```ucm:hide
.> builtins.merge
.> builtins.mergeio
.> cd builtin
```
```unison:hide
use .builtin.io2 Failure
a |> f = f a
startsWith : Text -> Text -> Boolean
startsWith prefix text = (eq (take (size prefix) text) prefix)
contains : Text -> Text -> Boolean
contains needle haystack = if (size haystack) == 0 then false else
if startsWith needle haystack then true else
contains needle (drop 1 haystack)
filter: (a -> Boolean) -> [a] -> [a]
filter f all =
go acc = cases
a +: as -> if (f a) then go (cons a acc) as else go acc as
[] -> acc
go [] all
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
-- generated with:
-- openssl req -newkey rsa:2048 -subj '/CN=test.unison.cloud/O=Unison/C=US' -nodes -keyout key.pem -x509 -days 365 -out cert.pem
self_signed_key_pem = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBrpp+SxjCz/aQ\nQXT2hKXrT3lZ3Ek1VT/kgNre3J/RUyNZjZnZXCMyNjZ4IxYKxcCAIgGtfFpgvkzT\n5NRGZKLlSX4Y8HayV3gdEXO9hq4w/i/s+I0tOAJkVtHolXrrziZ7/4NZwMTbbqhO\n5hHHhbtBIpvNSw8el3AY3APkQ37+wM3fbvyeuG0ekoLqTa371W+/Z7pOi7QXYGEa\n8YHSNkuRKY46C4Y8oeWHOlSbQKu151GLuyQu74bnecGDN4KBdz9nyyKCCTpBsJpU\ni9Ozq3cps5L8dnN1zUNgaTyWp9kO3vbaTU8MY7p/dc7hNJ8pmGtSiiSs1xvni4Xl\nCBXBesxTAgMBAAECggEAAUtfcPSjh7nIFhK562PbkAUJ9JXKT3bwZGCQFek3kDiU\nBecyXgeFnLJMDuV9IjlMHg8cH8Ky/+6FqOzglk/Z3tps41HIGU0IWnlhYqThySYJ\nv/WxS9oR+gWyhXFqTuUj0LRWdmUZa7YDnfNfrwuvwrGuhOK5iSTN9PyTchUZZi50\ntxcNS/C3rk63c7TZLfuwxwGoUCeJvZZ/rmeVchhsuoo3QdSW0Aee7UtFtnvBfLCK\nXKdz+3q49fLZlDyx9/olJh+TY7GuF+G/LSfyQGi85beQhkXUH8/gIQIRI8INIEPB\n0XeTlv7Sgw5upqplJvHCXjAa+jz/Mo87znXBTMoIIQKBgQDorAlZCjzKxGDIaZoD\nDBXYzhSnnIhthThW4edCQ9/ZnJpX4vdTw4FngW504d8SPStMCYeBeMt8iwTczI4W\nHfK+AlVTlPfH/9NnIVADqqr9kobJW6782MYSW2/758d+L5bq8NGATyh8nPll9joN\nYAk7tNO2bGO2bEk2DbZMf3qnOQKBgQDVGdD005kUT3D+DfgeLTGzwk/2wCCPjoJ5\n6fsjsFAeZWU/qioIB3xHt1w8NsT6O+7XOKp/GRbsvsJR9Z/zmA3Ii5yrHYn48UzM\n+UyGLv+2HCpO+8A3szz/aDhKIxNFpXyZzvOXdtqBwTQbICOW2WRWOOgDrS2W1i9l\nD69xRLqj6wKBgBW0xwJ5hAZen7DSuT2DiR46y45/efLNtN3WIV77OgzxIS0FzZEQ\n8ieX2Zgp4kevUTS8xtl7TXCG/6MhqjfB/31edltf0GXmJfC/GNneuCkD3HM4jHCm\nQIRB54aWrvPEuM2ePc08lUha1KGAgRXyWaoqSn4ASqUgIQxb5x/n3KdxAoGAXlD0\nyMc2Q2T9r5PjMwfxrYvc9GsIfkEmwmqqupr4etuJumnH/JHDkcI30nazK8WG6j6s\nR2CFYvby7m92AcxUnWQdTSbfwAycX0QfeGwoxSMmHpsR8hUkdC5ea4Gmr/aUdUbB\nTVJPV4p5U2AgIE3LONYq6iWlvdLCW0pb7hfrO00CgYAb8bXz9BNow4soEkSbQysg\n4sGAr1+iSPY+ErffKRDpcFRnWcQdnTfI4xd8bgnC6OZwVpLbRZaZf3opDJ+axWqa\nEgAeHErTDY4R8aMecvyQj780sQ35kVq4VK0rSQyiKRBcjEust8UEzwYsUog2ysN0\n3zLHVEvFTfwOSctnEQRw1w==\n-----END PRIVATE KEY-----\n"
self_signed_cert_pem = "-----BEGIN CERTIFICATE-----\nMIIDVTCCAj2gAwIBAgIUZI9WPZk1rv2Sx5XSK17BZSV4t7gwDQYJKoZIhvcNAQEL\nBQAwOjEaMBgGA1UEAwwRdGVzdC51bmlzb24uY2xvdWQxDzANBgNVBAoMBlVuaXNv\nbjELMAkGA1UEBhMCVVMwHhcNMjEwMTIyMDkxMTE3WhcNMjIwMTIyMDkxMTE3WjA6\nMRowGAYDVQQDDBF0ZXN0LnVuaXNvbi5jbG91ZDEPMA0GA1UECgwGVW5pc29uMQsw\nCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGumn5L\nGMLP9pBBdPaEpetPeVncSTVVP+SA2t7cn9FTI1mNmdlcIzI2NngjFgrFwIAiAa18\nWmC+TNPk1EZkouVJfhjwdrJXeB0Rc72GrjD+L+z4jS04AmRW0eiVeuvOJnv/g1nA\nxNtuqE7mEceFu0Eim81LDx6XcBjcA+RDfv7Azd9u/J64bR6SgupNrfvVb79nuk6L\ntBdgYRrxgdI2S5EpjjoLhjyh5Yc6VJtAq7XnUYu7JC7vhud5wYM3goF3P2fLIoIJ\nOkGwmlSL07Ordymzkvx2c3XNQ2BpPJan2Q7e9tpNTwxjun91zuE0nymYa1KKJKzX\nG+eLheUIFcF6zFMCAwEAAaNTMFEwHQYDVR0OBBYEFFE3RQYASDWtwSdXL+qtQrjy\nH4SZMB8GA1UdIwQYMBaAFFE3RQYASDWtwSdXL+qtQrjyH4SZMA8GA1UdEwEB/wQF\nMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH7D8W68cR0QvNNPugCY7lPvA/F94Qam\nwCC2A55edcagfjqFy77xx4Ljrd2TC19yiSzyeeJ+YuohbcipLce90BaaaiYq9aah\n5DICDCUhm1qbhJzqNB2Lqgl4aN+jRMeRVC+rpQBYBNmdPBqdv/k+T2uyghwmLWXe\n/AxCjCLA0zoYzOMytS86veD6CQbF5DpSTZx5dyZTeGhk2izhoM8cgiu+/7YncAbJ\nt7b7UT5Yu3+z1hAdUF5Q21bkEksGBC8UW0G0PMy8XNRMuMsz+2LC39u3u7QyX/+e\nuQGST3aCreV27zd0lrF8LHjwD2XcjVVzHy46VYQvf1r+6gatedDBjqc=\n-----END CERTIFICATE-----\n"
```
```ucm:hide
.> add
```
# Using an alternative certificate store
First lets make sure we can load our cert and private key
```unison
test> match (decodeCert (toUtf8 self_signed_cert_pem) with
Left (Failure _ t _) -> [Fail t]
Right _ -> [Ok "succesfully decoded self_signed_pem"]
```
Test handshaking a client/server a local TCP connection using our
self-signed cert.
We'll create a server and a client, and start threads for each.
The server will report the port it is bound to via a passed MVar which
the client can read.
```unison
serverThread: MVar Nat -> Text -> '{io2.IO}()
serverThread portVar toSend = 'let
go = 'let
-- load our self signed cert
cert = decodeCert (toUtf8 self_signed_cert_pem) |> toException
-- assume there is exactly one key decoded from our Bytes
key = match (decodePrivateKey (toUtf8 self_signed_key_pem)) with k +: _ -> k
-- create a default configuration using our credentials (certificate chain and key)
tlsconfig = Tls.ServerConfig.default [cert] key
-- Open a TCP server port:
-- we pass the special port "0" to mean "please find us an open port"
sock = serverSocket (Some "127.0.0.1") "0" |> toException
-- find out what port we got
port = socketPort sock |> toException
-- report the port back so that the client knows where to connect
MVar.put portVar port |> toException
-- start listening to the socket so that it starts accepting connections
listen sock |> toException
watch ("server listening on port: " ++ (toText port)) ()
-- accept a single connection on this socket
sock' = socketAccept sock |> toException
-- attach TLS to our TCP connection
tls = newServer tlsconfig sock' |> toException
-- try to handshake the TLS connection with the client
handshake tls |> toException
-- send our message over our tls channel
send tls (toUtf8 toSend) |> toException
terminate tls |> toException
closeSocket sock' |> toException
match (toEither go) with
Left (Failure _ t _) -> watch ("error in server: " ++ t) ()
_ -> watch "server finished" ()
testClient : Optional SignedCert -> Text -> MVar Nat -> '{io2.IO, Exception Failure} Text
testClient cert hostname portVar _ =
-- create a client that will expect a cert from the given hostname (CN)
defaultClient = (Tls.ClientConfig.default hostname Bytes.empty)
-- if we were passed a certificate to trust, it is the only certificate we trust
tlsconfig = match cert with
None -> defaultClient
Some (cert) -> defaultClient |> ClientConfig.certificates.set [cert]
-- wait to find out what port the server started on
port = take portVar |> toException
-- create a tcp connection with the server
sock = clientSocket "127.0.0.1" (Nat.toText port) |> toException
-- attach the TLS client to the TCP socket
tls = newClient tlsconfig sock |> toException
watch ("client connecting to port: " ++ (toText port)) ()
-- verify that the server presents us with a certificate chain for
-- test.unison.cloud originating with a certificate we trust, and
-- that the server can use a compatible TLS version and cipher
handshake tls |> toException
-- receive a message from the server
fromUtf8 (toException (receive tls)) |> toException
testConnectSelfSigned : '{io2.IO}[Result]
testConnectSelfSigned _ =
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
cert = decodeCert (toUtf8 self_signed_cert_pem) |> toException
received = !(testClient (Some cert) "test.unison.cloud" portVar)
expectU (a->a) "should have reaped what we've sown" toSend received
runTest test
-- this client will trust whatever certs the system trusts
-- for signing certs. This should NOT trust the server
-- serving the self-signed cert, so both the client and
-- the server should have a failure during the handshake
testCAReject : '{io2.IO}[Result]
testCAReject _ =
checkError : Either Failure a -> Result
checkError = cases
Right _ -> Fail "expected a handshake exception"
Left (Failure _ t _) ->
if contains "UnknownCa" t && contains "HandshakeFailed" t then Ok "correctly rejected self-signed cert" else
Fail ("expected UnknownCa, got: " ++ t)
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
testClient None "test.unison.cloud" portVar |> toEither |> checkError |> emit
runTest test
-- this client will ask for a different hostname, and
-- therefore should fail during the handshake when the
-- server presents an cert with unexpected hostname
testCNReject : '{io2.IO}[Result]
testCNReject _ =
checkError : Either Failure a -> Result
checkError = cases
Right _ -> Fail "expected a handshake exception"
Left (Failure _ t _) -> if contains "NameMismatch" t && contains "HandshakeFailed" t then Ok "correctly rejected self-signed cert" else
Fail ("expected UnknownCa, got: " ++ t)
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
testClient None "wrong.host.name" portVar |> toEither |> checkError |> emit
runTest test
```
```ucm
.> add
.> io.test testConnectSelfSigned
.> io.test testCAReject
.> io.test testCNReject
```

View File

@ -0,0 +1,353 @@
# Tests for TLS builtins
## Setup
You can skip the section which is just needed to make the transcript self-contained.
```unison
use .builtin.io2 Failure
a |> f = f a
startsWith : Text -> Text -> Boolean
startsWith prefix text = (eq (take (size prefix) text) prefix)
contains : Text -> Text -> Boolean
contains needle haystack = if (size haystack) == 0 then false else
if startsWith needle haystack then true else
contains needle (drop 1 haystack)
filter: (a -> Boolean) -> [a] -> [a]
filter f all =
go acc = cases
a +: as -> if (f a) then go (cons a acc) as else go acc as
[] -> acc
go [] all
ability Exception e where raise : e ->{Exception e} a
toException : Either e a ->{Exception e} a
toException = cases
Left e -> raise e
Right a -> a
Exception.toEither.handler : Request {Exception e} a -> Either e a
Exception.toEither.handler = cases
{ a } -> Right a
{raise e -> _} -> Left e
Exception.toEither : '{g, Exception e} a ->{g} Either e a
Exception.toEither a = handle !a with Exception.toEither.handler
isNone = cases
Some _ -> false
None -> true
ability Stream a where
emit: a -> ()
Stream.toList.handler : Request {Stream a} r -> [a]
Stream.toList.handler =
go : [a] -> Request {Stream a} r -> [a]
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ _ } -> acc
go []
Stream.toList : '{Stream a} r -> [a]
Stream.toList s = handle !s with toList.handler
Stream.collect.handler : Request {Stream a} r -> ([a],r)
Stream.collect.handler =
go : [a] -> Request {Stream a} r -> ([a],r)
go acc = cases
{ Stream.emit a -> k } -> handle !k with go (acc :+ a)
{ r } -> (acc, r)
go []
Stream.collect : '{e, Stream a} r -> {e} ([a],r)
Stream.collect s =
handle !s with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, returns the Results and the result of the test
evalTest: '{Stream Result, Exception Failure, io2.IO} a -> ([Result], Either Failure a)
evalTest a = handle
(handle !a with Exception.toEither.handler)
with Stream.collect.handler
-- Run tests which might fail, might create temporary directores and Stream out
-- results, but ignore the produced value and only return the test Results
runTest: '{Stream Result, Exception Failure, io2.IO} a -> [Result]
runTest t = match evalTest t with
(results, Right _) -> results
(results, Left (Failure _ t _)) -> results :+ (Fail t)
--
-- convenience functions for emitting test results
--
expect : (a -> Text) -> (a -> a -> Boolean) -> Text -> a -> a -> {Stream Result} ()
expect toText compare msg expected actual = let
if (compare expected actual) then
emit (Ok msg)
else let
failMsg = msg ++ "expected : " ++ (toText expected) ++ " actual: " ++ (toText actual)
emit (Fail failMsg)
expectU : (a -> Text) -> Text -> a -> a -> {Stream Result} ()
expectU toText msg expected actual = expect toText (==) msg expected actual
check: Text -> Boolean -> {Stream Result} ()
check msg test = if test then emit (Ok msg) else emit (Fail msg)
-- generated with:
-- openssl req -newkey rsa:2048 -subj '/CN=test.unison.cloud/O=Unison/C=US' -nodes -keyout key.pem -x509 -days 365 -out cert.pem
self_signed_key_pem = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBrpp+SxjCz/aQ\nQXT2hKXrT3lZ3Ek1VT/kgNre3J/RUyNZjZnZXCMyNjZ4IxYKxcCAIgGtfFpgvkzT\n5NRGZKLlSX4Y8HayV3gdEXO9hq4w/i/s+I0tOAJkVtHolXrrziZ7/4NZwMTbbqhO\n5hHHhbtBIpvNSw8el3AY3APkQ37+wM3fbvyeuG0ekoLqTa371W+/Z7pOi7QXYGEa\n8YHSNkuRKY46C4Y8oeWHOlSbQKu151GLuyQu74bnecGDN4KBdz9nyyKCCTpBsJpU\ni9Ozq3cps5L8dnN1zUNgaTyWp9kO3vbaTU8MY7p/dc7hNJ8pmGtSiiSs1xvni4Xl\nCBXBesxTAgMBAAECggEAAUtfcPSjh7nIFhK562PbkAUJ9JXKT3bwZGCQFek3kDiU\nBecyXgeFnLJMDuV9IjlMHg8cH8Ky/+6FqOzglk/Z3tps41HIGU0IWnlhYqThySYJ\nv/WxS9oR+gWyhXFqTuUj0LRWdmUZa7YDnfNfrwuvwrGuhOK5iSTN9PyTchUZZi50\ntxcNS/C3rk63c7TZLfuwxwGoUCeJvZZ/rmeVchhsuoo3QdSW0Aee7UtFtnvBfLCK\nXKdz+3q49fLZlDyx9/olJh+TY7GuF+G/LSfyQGi85beQhkXUH8/gIQIRI8INIEPB\n0XeTlv7Sgw5upqplJvHCXjAa+jz/Mo87znXBTMoIIQKBgQDorAlZCjzKxGDIaZoD\nDBXYzhSnnIhthThW4edCQ9/ZnJpX4vdTw4FngW504d8SPStMCYeBeMt8iwTczI4W\nHfK+AlVTlPfH/9NnIVADqqr9kobJW6782MYSW2/758d+L5bq8NGATyh8nPll9joN\nYAk7tNO2bGO2bEk2DbZMf3qnOQKBgQDVGdD005kUT3D+DfgeLTGzwk/2wCCPjoJ5\n6fsjsFAeZWU/qioIB3xHt1w8NsT6O+7XOKp/GRbsvsJR9Z/zmA3Ii5yrHYn48UzM\n+UyGLv+2HCpO+8A3szz/aDhKIxNFpXyZzvOXdtqBwTQbICOW2WRWOOgDrS2W1i9l\nD69xRLqj6wKBgBW0xwJ5hAZen7DSuT2DiR46y45/efLNtN3WIV77OgzxIS0FzZEQ\n8ieX2Zgp4kevUTS8xtl7TXCG/6MhqjfB/31edltf0GXmJfC/GNneuCkD3HM4jHCm\nQIRB54aWrvPEuM2ePc08lUha1KGAgRXyWaoqSn4ASqUgIQxb5x/n3KdxAoGAXlD0\nyMc2Q2T9r5PjMwfxrYvc9GsIfkEmwmqqupr4etuJumnH/JHDkcI30nazK8WG6j6s\nR2CFYvby7m92AcxUnWQdTSbfwAycX0QfeGwoxSMmHpsR8hUkdC5ea4Gmr/aUdUbB\nTVJPV4p5U2AgIE3LONYq6iWlvdLCW0pb7hfrO00CgYAb8bXz9BNow4soEkSbQysg\n4sGAr1+iSPY+ErffKRDpcFRnWcQdnTfI4xd8bgnC6OZwVpLbRZaZf3opDJ+axWqa\nEgAeHErTDY4R8aMecvyQj780sQ35kVq4VK0rSQyiKRBcjEust8UEzwYsUog2ysN0\n3zLHVEvFTfwOSctnEQRw1w==\n-----END PRIVATE KEY-----\n"
self_signed_cert_pem = "-----BEGIN CERTIFICATE-----\nMIIDVTCCAj2gAwIBAgIUZI9WPZk1rv2Sx5XSK17BZSV4t7gwDQYJKoZIhvcNAQEL\nBQAwOjEaMBgGA1UEAwwRdGVzdC51bmlzb24uY2xvdWQxDzANBgNVBAoMBlVuaXNv\nbjELMAkGA1UEBhMCVVMwHhcNMjEwMTIyMDkxMTE3WhcNMjIwMTIyMDkxMTE3WjA6\nMRowGAYDVQQDDBF0ZXN0LnVuaXNvbi5jbG91ZDEPMA0GA1UECgwGVW5pc29uMQsw\nCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGumn5L\nGMLP9pBBdPaEpetPeVncSTVVP+SA2t7cn9FTI1mNmdlcIzI2NngjFgrFwIAiAa18\nWmC+TNPk1EZkouVJfhjwdrJXeB0Rc72GrjD+L+z4jS04AmRW0eiVeuvOJnv/g1nA\nxNtuqE7mEceFu0Eim81LDx6XcBjcA+RDfv7Azd9u/J64bR6SgupNrfvVb79nuk6L\ntBdgYRrxgdI2S5EpjjoLhjyh5Yc6VJtAq7XnUYu7JC7vhud5wYM3goF3P2fLIoIJ\nOkGwmlSL07Ordymzkvx2c3XNQ2BpPJan2Q7e9tpNTwxjun91zuE0nymYa1KKJKzX\nG+eLheUIFcF6zFMCAwEAAaNTMFEwHQYDVR0OBBYEFFE3RQYASDWtwSdXL+qtQrjy\nH4SZMB8GA1UdIwQYMBaAFFE3RQYASDWtwSdXL+qtQrjyH4SZMA8GA1UdEwEB/wQF\nMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH7D8W68cR0QvNNPugCY7lPvA/F94Qam\nwCC2A55edcagfjqFy77xx4Ljrd2TC19yiSzyeeJ+YuohbcipLce90BaaaiYq9aah\n5DICDCUhm1qbhJzqNB2Lqgl4aN+jRMeRVC+rpQBYBNmdPBqdv/k+T2uyghwmLWXe\n/AxCjCLA0zoYzOMytS86veD6CQbF5DpSTZx5dyZTeGhk2izhoM8cgiu+/7YncAbJ\nt7b7UT5Yu3+z1hAdUF5Q21bkEksGBC8UW0G0PMy8XNRMuMsz+2LC39u3u7QyX/+e\nuQGST3aCreV27zd0lrF8LHjwD2XcjVVzHy46VYQvf1r+6gatedDBjqc=\n-----END CERTIFICATE-----\n"
```
# Using an alternative certificate store
First lets make sure we can load our cert and private key
```unison
test> match (decodeCert (toUtf8 self_signed_cert_pem) with
Left (Failure _ t _) -> [Fail t]
Right _ -> [Ok "succesfully decoded self_signed_pem"]
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
test.ckc3ihvvem (Unison bug, unknown term)
Now evaluating any watch expressions (lines starting with
`>`)... Ctrl+C cancels.
1 | test> match (decodeCert (toUtf8 self_signed_cert_pem) with
✅ Passed succesfully decoded self_signed_pem
```
Test handshaking a client/server a local TCP connection using our
self-signed cert.
We'll create a server and a client, and start threads for each.
The server will report the port it is bound to via a passed MVar which
the client can read.
```unison
serverThread: MVar Nat -> Text -> '{io2.IO}()
serverThread portVar toSend = 'let
go = 'let
-- load our self signed cert
cert = decodeCert (toUtf8 self_signed_cert_pem) |> toException
-- assume there is exactly one key decoded from our Bytes
key = match (decodePrivateKey (toUtf8 self_signed_key_pem)) with k +: _ -> k
-- create a default configuration using our credentials (certificate chain and key)
tlsconfig = Tls.ServerConfig.default [cert] key
-- Open a TCP server port:
-- we pass the special port "0" to mean "please find us an open port"
sock = serverSocket (Some "127.0.0.1") "0" |> toException
-- find out what port we got
port = socketPort sock |> toException
-- report the port back so that the client knows where to connect
MVar.put portVar port |> toException
-- start listening to the socket so that it starts accepting connections
listen sock |> toException
watch ("server listening on port: " ++ (toText port)) ()
-- accept a single connection on this socket
sock' = socketAccept sock |> toException
-- attach TLS to our TCP connection
tls = newServer tlsconfig sock' |> toException
-- try to handshake the TLS connection with the client
handshake tls |> toException
-- send our message over our tls channel
send tls (toUtf8 toSend) |> toException
terminate tls |> toException
closeSocket sock' |> toException
match (toEither go) with
Left (Failure _ t _) -> watch ("error in server: " ++ t) ()
_ -> watch "server finished" ()
testClient : Optional SignedCert -> Text -> MVar Nat -> '{io2.IO, Exception Failure} Text
testClient cert hostname portVar _ =
-- create a client that will expect a cert from the given hostname (CN)
defaultClient = (Tls.ClientConfig.default hostname Bytes.empty)
-- if we were passed a certificate to trust, it is the only certificate we trust
tlsconfig = match cert with
None -> defaultClient
Some (cert) -> defaultClient |> ClientConfig.certificates.set [cert]
-- wait to find out what port the server started on
port = take portVar |> toException
-- create a tcp connection with the server
sock = clientSocket "127.0.0.1" (Nat.toText port) |> toException
-- attach the TLS client to the TCP socket
tls = newClient tlsconfig sock |> toException
watch ("client connecting to port: " ++ (toText port)) ()
-- verify that the server presents us with a certificate chain for
-- test.unison.cloud originating with a certificate we trust, and
-- that the server can use a compatible TLS version and cipher
handshake tls |> toException
-- receive a message from the server
fromUtf8 (toException (receive tls)) |> toException
testConnectSelfSigned : '{io2.IO}[Result]
testConnectSelfSigned _ =
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
cert = decodeCert (toUtf8 self_signed_cert_pem) |> toException
received = !(testClient (Some cert) "test.unison.cloud" portVar)
expectU (a->a) "should have reaped what we've sown" toSend received
runTest test
-- this client will trust whatever certs the system trusts
-- for signing certs. This should NOT trust the server
-- serving the self-signed cert, so both the client and
-- the server should have a failure during the handshake
testCAReject : '{io2.IO}[Result]
testCAReject _ =
checkError : Either Failure a -> Result
checkError = cases
Right _ -> Fail "expected a handshake exception"
Left (Failure _ t _) ->
if contains "UnknownCa" t && contains "HandshakeFailed" t then Ok "correctly rejected self-signed cert" else
Fail ("expected UnknownCa, got: " ++ t)
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
testClient None "test.unison.cloud" portVar |> toEither |> checkError |> emit
runTest test
-- this client will ask for a different hostname, and
-- therefore should fail during the handshake when the
-- server presents an cert with unexpected hostname
testCNReject : '{io2.IO}[Result]
testCNReject _ =
checkError : Either Failure a -> Result
checkError = cases
Right _ -> Fail "expected a handshake exception"
Left (Failure _ t _) -> if contains "NameMismatch" t && contains "HandshakeFailed" t then Ok "correctly rejected self-signed cert" else
Fail ("expected UnknownCa, got: " ++ t)
test _ =
-- Server
portVar = !MVar.newEmpty
toSend = "12345"
forkComp (serverThread portVar toSend)
-- Client
testClient None "wrong.host.name" portVar |> toEither |> checkError |> emit
runTest test
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
serverThread : MVar Nat -> Text -> '{io2.IO} ()
testCAReject : '{io2.IO} [Result]
testCNReject : '{io2.IO} [Result]
testClient : Optional SignedCert
-> Text
-> MVar Nat
-> '{io2.IO, Exception Failure} Text
testConnectSelfSigned : '{io2.IO} [Result]
```
```ucm
.> add
⍟ I've added these definitions:
serverThread : MVar Nat -> Text -> '{io2.IO} ()
testCAReject : '{io2.IO} [Result]
testCNReject : '{io2.IO} [Result]
testClient : Optional SignedCert
-> Text
-> MVar Nat
-> '{io2.IO, Exception Failure} Text
testConnectSelfSigned : '{io2.IO} [Result]
.> io.test testConnectSelfSigned
New test results:
◉ testConnectSelfSigned should have reaped what we've sown
✅ 1 test(s) passing
Tip: Use view testConnectSelfSigned to view the source of a
test.
.> io.test testCAReject
New test results:
◉ testCAReject correctly rejected self-signed cert
✅ 1 test(s) passing
Tip: Use view testCAReject to view the source of a test.
.> io.test testCNReject
New test results:
◉ testCNReject correctly rejected self-signed cert
✅ 1 test(s) passing
Tip: Use view testCNReject to view the source of a test.
```

View File

@ -54,6 +54,6 @@ greek_bytes = Bytes.fromList [206, 145, 206, 146, 206, 147, 206, 148, 206]
-- Its an error if we drop the first byte
> match fromUtf8 (drop 1 greek_bytes) with
Left (Failure _ t) -> t
Left (Failure _ t _) -> t
```

View File

@ -108,7 +108,7 @@ greek_bytes = Bytes.fromList [206, 145, 206, 146, 206, 147, 206, 148, 206]
-- Its an error if we drop the first byte
> match fromUtf8 (drop 1 greek_bytes) with
Left (Failure _ t) -> t
Left (Failure _ t _) -> t
```

View File

@ -19,10 +19,30 @@ pat7 x y = cases
(p1, _) | p1 == 9 -> (x + y : Nat, p1)
(p1, _) | true -> (0, p1)
bpat = cases
false -> 0
true -> 1
npat = cases
0 -> 1
_ -> 0
ipat = cases
+1 -> -1
-1 -> +1
_ -> +0
> (pat1 0 1 (2, 3),
pat2 0 1 "hi",
pat3 0 1 (2, 3),
pat4 0 1 (2, 3),
pat5 0 1 (3, 2),
pat6 1 2 (3, 4),
pat7 1 2 (20, 10))
pat7 1 2 (20, 10),
bpat false,
bpat true,
npat 0,
npat 33,
ipat +1,
ipat -1,
ipat -33)

View File

@ -4,4 +4,11 @@
(0, 1, 2),
(0, 1, 2),
(3, 3),
(0, 20))
(0, 20),
0,
1,
1,
0,
-1,
+1,
+0)

View File

@ -128,234 +128,280 @@ Let's try it!
108. Int.trailingZeros : Int -> Nat
109. Int.truncate0 : Int -> Nat
110. Int.xor : Int -> Int -> Int
111. unique type Link
112. builtin type Link.Term
113. Link.Term : Term -> Link
114. builtin type Link.Type
115. Link.Type : Type -> Link
116. builtin type List
117. List.++ : [a] -> [a] -> [a]
118. List.+: : a -> [a] -> [a]
119. List.:+ : [a] -> a -> [a]
120. List.at : Nat -> [a] -> Optional a
121. List.cons : a -> [a] -> [a]
122. List.drop : Nat -> [a] -> [a]
123. List.empty : [a]
124. List.size : [a] -> Nat
125. List.snoc : [a] -> a -> [a]
126. List.take : Nat -> [a] -> [a]
127. builtin type Nat
128. Nat.* : Nat -> Nat -> Nat
129. Nat.+ : Nat -> Nat -> Nat
130. Nat./ : Nat -> Nat -> Nat
131. Nat.and : Nat -> Nat -> Nat
132. Nat.complement : Nat -> Nat
133. Nat.drop : Nat -> Nat -> Nat
134. Nat.eq : Nat -> Nat -> Boolean
135. Nat.fromText : Text -> Optional Nat
136. Nat.gt : Nat -> Nat -> Boolean
137. Nat.gteq : Nat -> Nat -> Boolean
138. Nat.increment : Nat -> Nat
139. Nat.isEven : Nat -> Boolean
140. Nat.isOdd : Nat -> Boolean
141. Nat.leadingZeros : Nat -> Nat
142. Nat.lt : Nat -> Nat -> Boolean
143. Nat.lteq : Nat -> Nat -> Boolean
144. Nat.mod : Nat -> Nat -> Nat
145. Nat.or : Nat -> Nat -> Nat
146. Nat.popCount : Nat -> Nat
147. Nat.pow : Nat -> Nat -> Nat
148. Nat.shiftLeft : Nat -> Nat -> Nat
149. Nat.shiftRight : Nat -> Nat -> Nat
150. Nat.sub : Nat -> Nat -> Int
151. Nat.toFloat : Nat -> Float
152. Nat.toInt : Nat -> Int
153. Nat.toText : Nat -> Text
154. Nat.trailingZeros : Nat -> Nat
155. Nat.xor : Nat -> Nat -> Nat
156. type Optional a
157. Optional.None : Optional a
158. Optional.Some : a -> Optional a
159. builtin type Request
160. type SeqView a b
161. SeqView.VElem : a -> b -> SeqView a b
162. SeqView.VEmpty : SeqView a b
163. unique type Test.Result
164. Test.Result.Fail : Text -> Result
165. Test.Result.Ok : Text -> Result
166. builtin type Text
167. Text.!= : Text -> Text -> Boolean
168. Text.++ : Text -> Text -> Text
169. Text.drop : Nat -> Text -> Text
170. Text.empty : Text
171. Text.eq : Text -> Text -> Boolean
172. Text.fromCharList : [Char] -> Text
173. Text.fromUtf8 : Bytes -> Either Failure Text
174. Text.gt : Text -> Text -> Boolean
175. Text.gteq : Text -> Text -> Boolean
176. Text.lt : Text -> Text -> Boolean
177. Text.lteq : Text -> Text -> Boolean
178. Text.size : Text -> Nat
179. Text.take : Nat -> Text -> Text
180. Text.toCharList : Text -> [Char]
181. Text.toUtf8 : Text -> Bytes
182. Text.uncons : Text -> Optional (Char, Text)
183. Text.unsnoc : Text -> Optional (Text, Char)
184. type Tuple a b
185. Tuple.Cons : a -> b -> Tuple a b
186. type Unit
187. Unit.Unit : ()
188. Universal.< : a -> a -> Boolean
189. Universal.<= : a -> a -> Boolean
190. Universal.== : a -> a -> Boolean
191. Universal.> : a -> a -> Boolean
192. Universal.>= : a -> a -> Boolean
193. Universal.compare : a -> a -> Int
194. builtin type Value
195. Value.dependencies : Value -> [Term]
196. Value.deserialize : Bytes -> Either Text Value
197. Value.load : Value ->{IO} Either [Term] a
198. Value.serialize : Value -> Bytes
199. Value.value : a -> Value
200. bug : a -> b
201. builtin type crypto.HashAlgorithm
202. crypto.HashAlgorithm.Blake2b_256 : HashAlgorithm
203. crypto.HashAlgorithm.Blake2b_512 : HashAlgorithm
204. crypto.HashAlgorithm.Blake2s_256 : HashAlgorithm
205. crypto.HashAlgorithm.Sha2_256 : HashAlgorithm
206. crypto.HashAlgorithm.Sha2_512 : HashAlgorithm
207. crypto.HashAlgorithm.Sha3_256 : HashAlgorithm
208. crypto.HashAlgorithm.Sha3_512 : HashAlgorithm
209. crypto.hash : HashAlgorithm -> a -> Bytes
210. crypto.hashBytes : HashAlgorithm -> Bytes -> Bytes
211. crypto.hmac : HashAlgorithm -> Bytes -> a -> Bytes
212. crypto.hmacBytes : HashAlgorithm
111. unique type IsPropagated
112. IsPropagated.IsPropagated : IsPropagated
113. unique type IsTest
114. IsTest.IsTest : IsTest
115. unique type Link
116. builtin type Link.Term
117. Link.Term : Term -> Link
118. builtin type Link.Type
119. Link.Type : Type -> Link
120. builtin type List
121. List.++ : [a] -> [a] -> [a]
122. List.+: : a -> [a] -> [a]
123. List.:+ : [a] -> a -> [a]
124. List.at : Nat -> [a] -> Optional a
125. List.cons : a -> [a] -> [a]
126. List.drop : Nat -> [a] -> [a]
127. List.empty : [a]
128. List.size : [a] -> Nat
129. List.snoc : [a] -> a -> [a]
130. List.take : Nat -> [a] -> [a]
131. builtin type Nat
132. Nat.* : Nat -> Nat -> Nat
133. Nat.+ : Nat -> Nat -> Nat
134. Nat./ : Nat -> Nat -> Nat
135. Nat.and : Nat -> Nat -> Nat
136. Nat.complement : Nat -> Nat
137. Nat.drop : Nat -> Nat -> Nat
138. Nat.eq : Nat -> Nat -> Boolean
139. Nat.fromText : Text -> Optional Nat
140. Nat.gt : Nat -> Nat -> Boolean
141. Nat.gteq : Nat -> Nat -> Boolean
142. Nat.increment : Nat -> Nat
143. Nat.isEven : Nat -> Boolean
144. Nat.isOdd : Nat -> Boolean
145. Nat.leadingZeros : Nat -> Nat
146. Nat.lt : Nat -> Nat -> Boolean
147. Nat.lteq : Nat -> Nat -> Boolean
148. Nat.mod : Nat -> Nat -> Nat
149. Nat.or : Nat -> Nat -> Nat
150. Nat.popCount : Nat -> Nat
151. Nat.pow : Nat -> Nat -> Nat
152. Nat.shiftLeft : Nat -> Nat -> Nat
153. Nat.shiftRight : Nat -> Nat -> Nat
154. Nat.sub : Nat -> Nat -> Int
155. Nat.toFloat : Nat -> Float
156. Nat.toInt : Nat -> Int
157. Nat.toText : Nat -> Text
158. Nat.trailingZeros : Nat -> Nat
159. Nat.xor : Nat -> Nat -> Nat
160. type Optional a
161. Optional.None : Optional a
162. Optional.Some : a -> Optional a
163. builtin type Request
164. type SeqView a b
165. SeqView.VElem : a -> b -> SeqView a b
166. SeqView.VEmpty : SeqView a b
167. unique type Test.Result
168. Test.Result.Fail : Text -> Result
169. Test.Result.Ok : Text -> Result
170. builtin type Text
171. Text.!= : Text -> Text -> Boolean
172. Text.++ : Text -> Text -> Text
173. Text.drop : Nat -> Text -> Text
174. Text.empty : Text
175. Text.eq : Text -> Text -> Boolean
176. Text.fromCharList : [Char] -> Text
177. Text.fromUtf8 : Bytes -> Either Failure Text
178. Text.gt : Text -> Text -> Boolean
179. Text.gteq : Text -> Text -> Boolean
180. Text.lt : Text -> Text -> Boolean
181. Text.lteq : Text -> Text -> Boolean
182. Text.size : Text -> Nat
183. Text.take : Nat -> Text -> Text
184. Text.toCharList : Text -> [Char]
185. Text.toUtf8 : Text -> Bytes
186. Text.uncons : Text -> Optional (Char, Text)
187. Text.unsnoc : Text -> Optional (Text, Char)
188. type Tuple a b
189. Tuple.Cons : a -> b -> Tuple a b
190. type Unit
191. Unit.Unit : ()
192. Universal.< : a -> a -> Boolean
193. Universal.<= : a -> a -> Boolean
194. Universal.== : a -> a -> Boolean
195. Universal.> : a -> a -> Boolean
196. Universal.>= : a -> a -> Boolean
197. Universal.compare : a -> a -> Int
198. builtin type Value
199. Value.dependencies : Value -> [Term]
200. Value.deserialize : Bytes -> Either Text Value
201. Value.load : Value ->{IO} Either [Term] a
202. Value.serialize : Value -> Bytes
203. Value.value : a -> Value
204. bug : a -> b
205. builtin type crypto.HashAlgorithm
206. crypto.HashAlgorithm.Blake2b_256 : HashAlgorithm
207. crypto.HashAlgorithm.Blake2b_512 : HashAlgorithm
208. crypto.HashAlgorithm.Blake2s_256 : HashAlgorithm
209. crypto.HashAlgorithm.Sha2_256 : HashAlgorithm
210. crypto.HashAlgorithm.Sha2_512 : HashAlgorithm
211. crypto.HashAlgorithm.Sha3_256 : HashAlgorithm
212. crypto.HashAlgorithm.Sha3_512 : HashAlgorithm
213. crypto.hash : HashAlgorithm -> a -> Bytes
214. crypto.hashBytes : HashAlgorithm -> Bytes -> Bytes
215. crypto.hmac : HashAlgorithm -> Bytes -> a -> Bytes
216. crypto.hmacBytes : HashAlgorithm
-> Bytes
-> Bytes
-> Bytes
213. unique type io2.BufferMode
214. io2.BufferMode.BlockBuffering : BufferMode
215. io2.BufferMode.LineBuffering : BufferMode
216. io2.BufferMode.NoBuffering : BufferMode
217. io2.BufferMode.SizedBlockBuffering : Nat -> BufferMode
218. unique type io2.Failure
219. io2.Failure.Failure : Type -> Text -> Failure
220. unique type io2.FileMode
221. io2.FileMode.Append : FileMode
222. io2.FileMode.Read : FileMode
223. io2.FileMode.ReadWrite : FileMode
224. io2.FileMode.Write : FileMode
225. builtin type io2.Handle
226. builtin type io2.IO
227. io2.IO.clientSocket : Text
217. unique type io2.BufferMode
218. io2.BufferMode.BlockBuffering : BufferMode
219. io2.BufferMode.LineBuffering : BufferMode
220. io2.BufferMode.NoBuffering : BufferMode
221. io2.BufferMode.SizedBlockBuffering : Nat -> BufferMode
222. unique type io2.Failure
223. io2.Failure.Failure : Type -> Text -> Any -> Failure
224. unique type io2.FileMode
225. io2.FileMode.Append : FileMode
226. io2.FileMode.Read : FileMode
227. io2.FileMode.ReadWrite : FileMode
228. io2.FileMode.Write : FileMode
229. builtin type io2.Handle
230. builtin type io2.IO
231. io2.IO.clientSocket : Text
-> Text
->{IO} Either Failure Socket
228. io2.IO.closeFile : Handle ->{IO} Either Failure ()
229. io2.IO.closeSocket : Socket ->{IO} Either Failure ()
230. io2.IO.createDirectory : Text ->{IO} Either Failure ()
231. io2.IO.createTempDirectory : Text
232. io2.IO.closeFile : Handle ->{IO} Either Failure ()
233. io2.IO.closeSocket : Socket ->{IO} Either Failure ()
234. io2.IO.createDirectory : Text ->{IO} Either Failure ()
235. io2.IO.createTempDirectory : Text
->{IO} Either Failure Text
232. io2.IO.delay : Nat ->{IO} Either Failure ()
233. io2.IO.fileExists : Text ->{IO} Either Failure Boolean
234. io2.IO.forkComp : '{IO} Either Failure a ->{IO} ThreadId
235. io2.IO.getBuffering : Handle
236. io2.IO.delay : Nat ->{IO} Either Failure ()
237. io2.IO.fileExists : Text ->{IO} Either Failure Boolean
238. io2.IO.forkComp : '{IO} a ->{IO} ThreadId
239. io2.IO.getBuffering : Handle
->{IO} Either Failure BufferMode
236. io2.IO.getBytes : Handle
240. io2.IO.getBytes : Handle
-> Nat
->{IO} Either Failure Bytes
237. io2.IO.getCurrentDirectory : '{IO} Either Failure Text
238. io2.IO.getFileSize : Text ->{IO} Either Failure Nat
239. io2.IO.getFileTimestamp : Text ->{IO} Either Failure Nat
240. io2.IO.getTempDirectory : '{IO} Either Failure Text
241. io2.IO.handlePosition : Handle ->{IO} Either Failure Int
242. io2.IO.isDirectory : Text ->{IO} Either Failure Boolean
243. io2.IO.isFileEOF : Handle ->{IO} Either Failure Boolean
244. io2.IO.isFileOpen : Handle ->{IO} Either Failure Boolean
245. io2.IO.isSeekable : Handle ->{IO} Either Failure Boolean
246. io2.IO.kill : ThreadId ->{IO} Either Failure ()
247. io2.IO.listen : Socket ->{IO} Either Failure ()
248. io2.IO.openFile : Text
241. io2.IO.getCurrentDirectory : '{IO} Either Failure Text
242. io2.IO.getFileSize : Text ->{IO} Either Failure Nat
243. io2.IO.getFileTimestamp : Text ->{IO} Either Failure Nat
244. io2.IO.getTempDirectory : '{IO} Either Failure Text
245. io2.IO.handlePosition : Handle ->{IO} Either Failure Nat
246. io2.IO.isDirectory : Text ->{IO} Either Failure Boolean
247. io2.IO.isFileEOF : Handle ->{IO} Either Failure Boolean
248. io2.IO.isFileOpen : Handle ->{IO} Either Failure Boolean
249. io2.IO.isSeekable : Handle ->{IO} Either Failure Boolean
250. io2.IO.kill : ThreadId ->{IO} Either Failure ()
251. io2.IO.listen : Socket ->{IO} Either Failure ()
252. io2.IO.openFile : Text
-> FileMode
->{IO} Either Failure Handle
249. io2.IO.putBytes : Handle
253. io2.IO.putBytes : Handle
-> Bytes
->{IO} Either Failure ()
250. io2.IO.removeDirectory : Text ->{IO} Either Failure ()
251. io2.IO.removeFile : Text ->{IO} Either Failure ()
252. io2.IO.renameDirectory : Text
254. io2.IO.removeDirectory : Text ->{IO} Either Failure ()
255. io2.IO.removeFile : Text ->{IO} Either Failure ()
256. io2.IO.renameDirectory : Text
-> Text
->{IO} Either Failure ()
253. io2.IO.renameFile : Text -> Text ->{IO} Either Failure ()
254. io2.IO.seekHandle : Handle
257. io2.IO.renameFile : Text -> Text ->{IO} Either Failure ()
258. io2.IO.seekHandle : Handle
-> SeekMode
-> Int
->{IO} Either Failure ()
255. io2.IO.serverSocket : Text
259. io2.IO.serverSocket : Optional Text
-> Text
->{IO} Either Failure Socket
256. io2.IO.setBuffering : Handle
260. io2.IO.setBuffering : Handle
-> BufferMode
->{IO} Either Failure ()
257. io2.IO.setCurrentDirectory : Text
261. io2.IO.setCurrentDirectory : Text
->{IO} Either Failure ()
258. io2.IO.socketAccept : Socket ->{IO} Either Failure Socket
259. io2.IO.socketReceive : Socket
262. io2.IO.socketAccept : Socket ->{IO} Either Failure Socket
263. io2.IO.socketPort : Socket ->{IO} Either Failure Nat
264. io2.IO.socketReceive : Socket
-> Nat
->{IO} Either Failure Bytes
260. io2.IO.socketSend : Socket
265. io2.IO.socketSend : Socket
-> Bytes
->{IO} Either Failure ()
261. io2.IO.stdHandle : StdHandle -> Handle
262. io2.IO.systemTime : '{IO} Either Failure Nat
263. unique type io2.IOError
264. io2.IOError.AlreadyExists : IOError
265. io2.IOError.EOF : IOError
266. io2.IOError.IllegalOperation : IOError
267. io2.IOError.NoSuchThing : IOError
268. io2.IOError.PermissionDenied : IOError
269. io2.IOError.ResourceBusy : IOError
270. io2.IOError.ResourceExhausted : IOError
271. io2.IOError.UserError : IOError
272. builtin type io2.MVar
273. io2.MVar.isEmpty : MVar a ->{IO} Boolean
274. io2.MVar.new : a ->{IO} MVar a
275. io2.MVar.newEmpty : '{IO} MVar a
276. io2.MVar.put : MVar a -> a ->{IO} Either Failure ()
277. io2.MVar.read : MVar a ->{IO} Either Failure a
278. io2.MVar.swap : MVar a -> a ->{IO} Either Failure a
279. io2.MVar.take : MVar a ->{IO} Either Failure a
280. io2.MVar.tryPut : MVar a -> a ->{IO} Boolean
281. io2.MVar.tryRead : MVar a ->{IO} Optional a
282. io2.MVar.tryTake : MVar a ->{IO} Optional a
283. unique type io2.SeekMode
284. io2.SeekMode.AbsoluteSeek : SeekMode
285. io2.SeekMode.RelativeSeek : SeekMode
286. io2.SeekMode.SeekFromEnd : SeekMode
287. builtin type io2.Socket
288. unique type io2.StdHandle
289. io2.StdHandle.StdErr : StdHandle
290. io2.StdHandle.StdIn : StdHandle
291. io2.StdHandle.StdOut : StdHandle
292. builtin type io2.ThreadId
293. builtin type io2.Tls
294. builtin type io2.Tls.ClientConfig
295. io2.Tls.Config.defaultClient : Text
266. io2.IO.stdHandle : StdHandle -> Handle
267. io2.IO.systemTime : '{IO} Either Failure Nat
268. unique type io2.IOError
269. io2.IOError.AlreadyExists : IOError
270. io2.IOError.EOF : IOError
271. io2.IOError.IllegalOperation : IOError
272. io2.IOError.NoSuchThing : IOError
273. io2.IOError.PermissionDenied : IOError
274. io2.IOError.ResourceBusy : IOError
275. io2.IOError.ResourceExhausted : IOError
276. io2.IOError.UserError : IOError
277. builtin type io2.IOFailure##IOFailure
278. unique type io2.IOFailure#gro
279. builtin type io2.MVar
280. io2.MVar.isEmpty : MVar a ->{IO} Boolean
281. io2.MVar.new : a ->{IO} MVar a
282. io2.MVar.newEmpty : '{IO} MVar a
283. io2.MVar.put : MVar a -> a ->{IO} Either Failure ()
284. io2.MVar.read : MVar a ->{IO} Either Failure a
285. io2.MVar.swap : MVar a -> a ->{IO} Either Failure a
286. io2.MVar.take : MVar a ->{IO} Either Failure a
287. io2.MVar.tryPut : MVar a -> a ->{IO} Boolean
288. io2.MVar.tryRead : MVar a ->{IO} Optional a
289. io2.MVar.tryTake : MVar a ->{IO} Optional a
290. builtin type io2.STM
291. io2.STM.atomically : '{STM} a ->{IO} a
292. io2.STM.retry : '{STM} a
293. unique type io2.SeekMode
294. io2.SeekMode.AbsoluteSeek : SeekMode
295. io2.SeekMode.RelativeSeek : SeekMode
296. io2.SeekMode.SeekFromEnd : SeekMode
297. builtin type io2.Socket
298. unique type io2.StdHandle
299. io2.StdHandle.StdErr : StdHandle
300. io2.StdHandle.StdIn : StdHandle
301. io2.StdHandle.StdOut : StdHandle
302. io2.TLS.ClientConfig.ciphers.set : [##Tls.Cipher]
-> ClientConfig
-> ClientConfig
303. builtin type io2.TVar
304. io2.TVar.new : a ->{STM} TVar a
305. io2.TVar.newIO : a ->{IO} TVar a
306. io2.TVar.read : TVar a ->{STM} a
307. io2.TVar.readIO : TVar a ->{IO} a
308. io2.TVar.swap : TVar a -> a ->{STM} a
309. io2.TVar.write : TVar a -> a ->{STM} ()
310. builtin type io2.ThreadId
311. builtin type io2.Tls
312. builtin type io2.Tls.ClientConfig
313. io2.Tls.ClientConfig.certificates.set : [SignedCert]
-> ClientConfig
-> ClientConfig
314. io2.Tls.ClientConfig.default : Text
-> Bytes
-> ClientConfig
296. io2.Tls.Config.defaultServer : ServerConfig
297. builtin type io2.Tls.ServerConfig
298. io2.Tls.handshake : Tls ->{IO} Either Failure ()
299. io2.Tls.newClient : ClientConfig
315. io2.Tls.ClientConfig.versions.set : [##Tls.Version]
-> ClientConfig
-> ClientConfig
316. builtin type io2.Tls.PrivateKey
317. builtin type io2.Tls.ServerConfig
318. io2.Tls.ServerConfig.certificates.set : [SignedCert]
-> ServerConfig
-> ServerConfig
319. io2.Tls.ServerConfig.ciphers.set : [##Tls.Cipher]
-> ServerConfig
-> ServerConfig
320. io2.Tls.ServerConfig.default : [SignedCert]
-> PrivateKey
-> ServerConfig
321. io2.Tls.ServerConfig.versions.set : [##Tls.Version]
-> ServerConfig
-> ServerConfig
322. builtin type io2.Tls.SignedCert
323. io2.Tls.decodeCert : Bytes -> Either Failure SignedCert
324. io2.Tls.decodePrivateKey : Bytes -> [PrivateKey]
325. io2.Tls.encodeCert : SignedCert -> Bytes
326. io2.Tls.encodePrivateKey : PrivateKey -> Bytes
327. io2.Tls.handshake : Tls ->{IO} Either Failure ()
328. io2.Tls.newClient : ClientConfig
-> Socket
->{IO} Either Failure Tls
300. io2.Tls.newServer : ServerConfig
329. io2.Tls.newServer : ServerConfig
-> Socket
->{IO} Either Failure Tls
301. io2.Tls.receive : Tls ->{IO} Either Failure Bytes
302. io2.Tls.send : Tls -> Bytes ->{IO} Either Failure ()
303. io2.Tls.terminate : Tls ->{IO} Either Failure ()
304. unique type io2.TlsFailure
305. todo : a -> b
330. io2.Tls.receive : Tls ->{IO} Either Failure Bytes
331. io2.Tls.send : Tls -> Bytes ->{IO} Either Failure ()
332. io2.Tls.terminate : Tls ->{IO} Either Failure ()
333. builtin type io2.TlsFailure##TlsFailure
334. unique type io2.TlsFailure#o6b
335. metadata.isPropagated : IsPropagated
336. metadata.isTest : IsTest
337. todo : a -> b
.builtin> alias.many 94-104 .mylib

View File

@ -9,49 +9,54 @@ The `builtins.merge` command adds the known builtins to a `builtin` subnamespace
.tmp> ls builtin
1. Any (builtin type)
2. Any/ (1 definition)
3. Boolean (builtin type)
4. Boolean/ (1 definition)
5. Bytes (builtin type)
6. Bytes/ (17 definitions)
7. Char (builtin type)
8. Char/ (2 definitions)
9. Code (builtin type)
10. Code/ (6 definitions)
11. Debug/ (1 definition)
12. Doc (type)
13. Doc/ (6 definitions)
14. Either (type)
15. Either/ (2 definitions)
16. Float (builtin type)
17. Float/ (36 definitions)
18. Int (builtin type)
19. Int/ (29 definitions)
20. Link (type)
21. Link/ (4 definitions)
22. List (builtin type)
23. List/ (10 definitions)
24. Nat (builtin type)
25. Nat/ (28 definitions)
26. Optional (type)
27. Optional/ (2 definitions)
28. Request (builtin type)
29. SeqView (type)
30. SeqView/ (2 definitions)
31. Test/ (3 definitions)
32. Text (builtin type)
33. Text/ (17 definitions)
34. Tuple (type)
35. Tuple/ (1 definition)
36. Unit (type)
37. Unit/ (1 definition)
38. Universal/ (6 definitions)
39. Value (builtin type)
40. Value/ (5 definitions)
41. bug (a -> b)
42. crypto/ (12 definitions)
43. io2/ (92 definitions)
44. todo (a -> b)
1. Any (builtin type)
2. Any/ (1 definition)
3. Boolean (builtin type)
4. Boolean/ (1 definition)
5. Bytes (builtin type)
6. Bytes/ (17 definitions)
7. Char (builtin type)
8. Char/ (2 definitions)
9. Code (builtin type)
10. Code/ (6 definitions)
11. Debug/ (1 definition)
12. Doc (type)
13. Doc/ (6 definitions)
14. Either (type)
15. Either/ (2 definitions)
16. Float (builtin type)
17. Float/ (36 definitions)
18. Int (builtin type)
19. Int/ (29 definitions)
20. IsPropagated (type)
21. IsPropagated/ (1 definition)
22. IsTest (type)
23. IsTest/ (1 definition)
24. Link (type)
25. Link/ (4 definitions)
26. List (builtin type)
27. List/ (10 definitions)
28. Nat (builtin type)
29. Nat/ (28 definitions)
30. Optional (type)
31. Optional/ (2 definitions)
32. Request (builtin type)
33. SeqView (type)
34. SeqView/ (2 definitions)
35. Test/ (3 definitions)
36. Text (builtin type)
37. Text/ (17 definitions)
38. Tuple (type)
39. Tuple/ (1 definition)
40. Unit (type)
41. Unit/ (1 definition)
42. Universal/ (6 definitions)
43. Value (builtin type)
44. Value/ (5 definitions)
45. bug (a -> b)
46. crypto/ (12 definitions)
47. io2/ (118 definitions)
48. metadata/ (2 definitions)
49. todo (a -> b)
```

View File

@ -44,3 +44,22 @@ type Foo = Foo | Bar
.> view.patch
```
```unison
bar = 3
type bar = Foo
```
```ucm
.> add
```
```unison
type bar = Foo | Bar
```
```ucm
.> update
.> view.patch
.> delete.type-replacement bar
.> view.patch
```

View File

@ -130,3 +130,75 @@ type Foo = Foo | Bar
This patch is empty.
```
```unison
bar = 3
type bar = Foo
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
type bar
bar : ##Nat
```
```ucm
.> add
⍟ I've added these definitions:
type bar
bar : ##Nat
```
```unison
type bar = Foo | Bar
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These names already exist. You can `update` them to your
new definition:
type bar
(also named Foo)
```
```ucm
.> update
⍟ I've updated these names to your new definition:
type bar
(also named Foo)
.> view.patch
Edited Types: bar#568rsi7o3g -> Foo
Tip: To remove entries from a patch, use
delete.term-replacement or delete.type-replacement, as
appropriate.
.> delete.type-replacement bar
Done.
.> view.patch
Edited Types: bar#568rsi7o3g -> Foo
Tip: To remove entries from a patch, use
delete.term-replacement or delete.type-replacement, as
appropriate.
```

View File

@ -23,7 +23,7 @@ Technically, the definitions all exist, but they have no names. `builtins.merge`
.foo> ls
1. builtin/ (305 definitions)
1. builtin/ (337 definitions)
```
And for a limited time, you can get even more builtin goodies:
@ -35,7 +35,7 @@ And for a limited time, you can get even more builtin goodies:
.foo> ls
1. builtin/ (468 definitions)
1. builtin/ (494 definitions)
```
More typically, you'd start out by pulling `base.

View File

@ -1,13 +1,13 @@
This transcript tests that UCM can always access the definition of
`IsPropagated`, which is used internally.
This transcript tests that UCM can always access the definition of
`IsPropagated`/`isPropagated`, which is used internally.
```ucm:hide
.> alias.term ##Nat.+ +
.> alias.type ##Nat Nat
```
y depends on x
```unison
`y` depends on `x`,
```unison:hide
x = 3
y = x + 1
```
@ -16,13 +16,13 @@ y = x + 1
.> add
```
```unison
so the `update` of `x` causes a propagated update of `y`, and UCM links the
`isPropagated` metadata to such resulting terms:
```unison:hide
x = 4
```
The `update` of `x` causes a propagated update of `y`, and UCM links the
`isPropagated` metadata to such resulting terms:
```ucm
.> update
.> links y
@ -37,4 +37,3 @@ by running `builtins.merge` to have UCM add names for them.
.> links y
.> view 1
```

View File

@ -1,24 +1,12 @@
This transcript tests that UCM can always access the definition of
`IsPropagated`, which is used internally.
This transcript tests that UCM can always access the definition of
`IsPropagated`/`isPropagated`, which is used internally.
y depends on x
`y` depends on `x`,
```unison
x = 3
y = x + 1
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
x : Nat
y : Nat
```
```ucm
.> add
@ -28,25 +16,13 @@ y = x + 1
y : Nat
```
so the `update` of `x` causes a propagated update of `y`, and UCM links the
`isPropagated` metadata to such resulting terms:
```unison
x = 4
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These names already exist. You can `update` them to your
new definition:
x : Nat
```
The `update` of `x` causes a propagated update of `y`, and UCM links the
`isPropagated` metadata to such resulting terms:
```ucm
.> update

View File

@ -0,0 +1,18 @@
This transcript tests that UCM can always access the definition of
`IsTest`/`isTest`, which is used internally.
```ucm
.> builtins.merge
```
```unison:hide
test> pass = [Ok "Passed"]
```
```ucm
.> add
.> links pass
.> display 1
```
The definition and type of `isTest` should exist.

View File

@ -0,0 +1,33 @@
This transcript tests that UCM can always access the definition of
`IsTest`/`isTest`, which is used internally.
```ucm
.> builtins.merge
Done.
```
```unison
test> pass = [Ok "Passed"]
```
```ucm
.> add
⍟ I've added these definitions:
pass : [Result]
.> links pass
1. builtin.metadata.isTest : IsTest
Tip: Try using `display 1` to display the first result or
`view 1` to view its source.
.> display 1
IsTest
```
The definition and type of `isTest should exist.

View File

@ -112,13 +112,13 @@ We can also delete the fork if we're done with it. (Don't worry, it's still in t
Note: The most recent namespace hash is immediately below this
message.
#aokhru3upu
#rq3jrbg57r
- Deletes:
feature1.y
#07lio3nger
#64jmhloeht
+ Adds / updates:
@ -129,26 +129,26 @@ We can also delete the fork if we're done with it. (Don't worry, it's still in t
Original name New name(s)
feature1.y master.y
#jh0ai3ctth
#ncr9hv1q57
+ Adds / updates:
feature1.y
#srgjtgfm9a
#bbcrond13i
> Moves:
Original name New name
x master.x
#p7j317tb68
#311k7aet5l
+ Adds / updates:
x
#ce9gjk322r
#e3bhlpkn5h
+ Adds / updates:
@ -193,19 +193,22 @@ We can also delete the fork if we're done with it. (Don't worry, it's still in t
builtin.Int.shiftRight builtin.Int.signum
builtin.Int.toFloat builtin.Int.toText
builtin.Int.trailingZeros builtin.Int.truncate0
builtin.Int.xor builtin.Link builtin.Link.Term##Link.Term
builtin.Link.Term#quh#0 builtin.Link.Type##Link.Type
builtin.Link.Type#quh#1 builtin.List builtin.List.++
builtin.List.+: builtin.List.:+ builtin.List.at
builtin.List.cons builtin.List.drop builtin.List.empty
builtin.List.size builtin.List.snoc builtin.List.take
builtin.Nat builtin.Nat.* builtin.Nat.+ builtin.Nat./
builtin.Nat.and builtin.Nat.complement builtin.Nat.drop
builtin.Nat.eq builtin.Nat.fromText builtin.Nat.gt
builtin.Nat.gteq builtin.Nat.increment builtin.Nat.isEven
builtin.Nat.isOdd builtin.Nat.leadingZeros builtin.Nat.lt
builtin.Nat.lteq builtin.Nat.mod builtin.Nat.or
builtin.Nat.popCount builtin.Nat.pow builtin.Nat.shiftLeft
builtin.Int.xor builtin.IsPropagated
builtin.IsPropagated.IsPropagated builtin.IsTest
builtin.IsTest.IsTest builtin.Link
builtin.Link.Term##Link.Term builtin.Link.Term#quh#0
builtin.Link.Type##Link.Type builtin.Link.Type#quh#1
builtin.List builtin.List.++ builtin.List.+:
builtin.List.:+ builtin.List.at builtin.List.cons
builtin.List.drop builtin.List.empty builtin.List.size
builtin.List.snoc builtin.List.take builtin.Nat
builtin.Nat.* builtin.Nat.+ builtin.Nat./ builtin.Nat.and
builtin.Nat.complement builtin.Nat.drop builtin.Nat.eq
builtin.Nat.fromText builtin.Nat.gt builtin.Nat.gteq
builtin.Nat.increment builtin.Nat.isEven builtin.Nat.isOdd
builtin.Nat.leadingZeros builtin.Nat.lt builtin.Nat.lteq
builtin.Nat.mod builtin.Nat.or builtin.Nat.popCount
builtin.Nat.pow builtin.Nat.shiftLeft
builtin.Nat.shiftRight builtin.Nat.sub builtin.Nat.toFloat
builtin.Nat.toInt builtin.Nat.toText
builtin.Nat.trailingZeros builtin.Nat.xor builtin.Optional
@ -263,35 +266,54 @@ We can also delete the fork if we're done with it. (Don't worry, it's still in t
builtin.io2.IO.renameFile builtin.io2.IO.seekHandle
builtin.io2.IO.serverSocket builtin.io2.IO.setBuffering
builtin.io2.IO.setCurrentDirectory
builtin.io2.IO.socketAccept builtin.io2.IO.socketReceive
builtin.io2.IO.socketSend builtin.io2.IO.stdHandle
builtin.io2.IO.systemTime builtin.io2.IOError
builtin.io2.IOError.AlreadyExists builtin.io2.IOError.EOF
builtin.io2.IO.socketAccept builtin.io2.IO.socketPort
builtin.io2.IO.socketReceive builtin.io2.IO.socketSend
builtin.io2.IO.stdHandle builtin.io2.IO.systemTime
builtin.io2.IOError builtin.io2.IOError.AlreadyExists
builtin.io2.IOError.EOF
builtin.io2.IOError.IllegalOperation
builtin.io2.IOError.NoSuchThing
builtin.io2.IOError.PermissionDenied
builtin.io2.IOError.ResourceBusy
builtin.io2.IOError.ResourceExhausted
builtin.io2.IOError.UserError builtin.io2.MVar
builtin.io2.MVar.isEmpty builtin.io2.MVar.new
builtin.io2.MVar.newEmpty builtin.io2.MVar.put
builtin.io2.MVar.read builtin.io2.MVar.swap
builtin.io2.MVar.take builtin.io2.MVar.tryPut
builtin.io2.MVar.tryRead builtin.io2.MVar.tryTake
builtin.io2.IOError.UserError
builtin.io2.IOFailure##IOFailure builtin.io2.IOFailure#gro
builtin.io2.MVar builtin.io2.MVar.isEmpty
builtin.io2.MVar.new builtin.io2.MVar.newEmpty
builtin.io2.MVar.put builtin.io2.MVar.read
builtin.io2.MVar.swap builtin.io2.MVar.take
builtin.io2.MVar.tryPut builtin.io2.MVar.tryRead
builtin.io2.MVar.tryTake builtin.io2.STM
builtin.io2.STM.atomically builtin.io2.STM.retry
builtin.io2.SeekMode builtin.io2.SeekMode.AbsoluteSeek
builtin.io2.SeekMode.RelativeSeek
builtin.io2.SeekMode.SeekFromEnd builtin.io2.Socket
builtin.io2.StdHandle builtin.io2.StdHandle.StdErr
builtin.io2.StdHandle.StdIn builtin.io2.StdHandle.StdOut
builtin.io2.TLS.ClientConfig.ciphers.set builtin.io2.TVar
builtin.io2.TVar.new builtin.io2.TVar.newIO
builtin.io2.TVar.read builtin.io2.TVar.readIO
builtin.io2.TVar.swap builtin.io2.TVar.write
builtin.io2.ThreadId builtin.io2.Tls
builtin.io2.Tls.ClientConfig
builtin.io2.Tls.Config.defaultClient
builtin.io2.Tls.Config.defaultServer
builtin.io2.Tls.ServerConfig builtin.io2.Tls.handshake
builtin.io2.Tls.ClientConfig.certificates.set
builtin.io2.Tls.ClientConfig.default
builtin.io2.Tls.ClientConfig.versions.set
builtin.io2.Tls.PrivateKey builtin.io2.Tls.ServerConfig
builtin.io2.Tls.ServerConfig.certificates.set
builtin.io2.Tls.ServerConfig.ciphers.set
builtin.io2.Tls.ServerConfig.default
builtin.io2.Tls.ServerConfig.versions.set
builtin.io2.Tls.SignedCert builtin.io2.Tls.decodeCert
builtin.io2.Tls.decodePrivateKey
builtin.io2.Tls.encodeCert
builtin.io2.Tls.encodePrivateKey builtin.io2.Tls.handshake
builtin.io2.Tls.newClient builtin.io2.Tls.newServer
builtin.io2.Tls.receive builtin.io2.Tls.send
builtin.io2.Tls.terminate builtin.io2.TlsFailure
builtin.todo
builtin.io2.Tls.terminate
builtin.io2.TlsFailure##TlsFailure
builtin.io2.TlsFailure#o6b builtin.metadata.isPropagated
builtin.metadata.isTest builtin.todo
#7asfbtqmoj (start of history)

View File

@ -59,16 +59,16 @@ y = 2
most recent, along with the command that got us there. Try:
`fork 2 .old`
`fork #40kdvebdr6 .old` to make an old namespace
`fork #90u1kgvkts .old` to make an old namespace
accessible again,
`reset-root #40kdvebdr6` to reset the root namespace and
`reset-root #90u1kgvkts` to reset the root namespace and
its history to that of the
specified namespace.
1. #ootu4soq9i : add
2. #40kdvebdr6 : add
3. #ce9gjk322r : builtins.merge
1. #q752mbqfhn : add
2. #90u1kgvkts : add
3. #e3bhlpkn5h : builtins.merge
4. #7asfbtqmoj : (initial reflogged namespace)
```

View File

@ -13,7 +13,7 @@ Let's look at some examples. We'll start with a namespace with just the builtins
#m40j6qtka9 (start of history)
#1jd6dhvs8p (start of history)
.> fork builtin builtin2
@ -42,21 +42,21 @@ Now suppose we `fork` a copy of builtin, then rename `Nat.+` to `frobnicate`, th
Note: The most recent namespace hash is immediately below this
message.
#mlkvjjn8bi
#878lsss0qo
> Moves:
Original name New name
Nat.frobnicate Nat.+
#vo8lt4lm8m
#brr50llg52
> Moves:
Original name New name
Nat.+ Nat.frobnicate
#m40j6qtka9 (start of history)
#1jd6dhvs8p (start of history)
```
If we merge that back into `builtin`, we get that same chain of history:
@ -71,21 +71,21 @@ If we merge that back into `builtin`, we get that same chain of history:
Note: The most recent namespace hash is immediately below this
message.
#mlkvjjn8bi
#878lsss0qo
> Moves:
Original name New name
Nat.frobnicate Nat.+
#vo8lt4lm8m
#brr50llg52
> Moves:
Original name New name
Nat.+ Nat.frobnicate
#m40j6qtka9 (start of history)
#1jd6dhvs8p (start of history)
```
Let's try again, but using a `merge.squash` (or just `squash`) instead. The history will be unchanged:
@ -106,7 +106,7 @@ Let's try again, but using a `merge.squash` (or just `squash`) instead. The hist
#m40j6qtka9 (start of history)
#1jd6dhvs8p (start of history)
```
The churn that happened in `mybuiltin` namespace ended up back in the same spot, so the squash merge of that namespace with our original namespace had no effect.