Skip to content

Commit

Permalink
Merge pull request #5456 from hummy123/main
Browse files Browse the repository at this point in the history
Perform minimal type checking for dictSet function,
  • Loading branch information
StachuDotNet authored Mar 6, 2025
2 parents 0bb7a7e + 5004a37 commit db9298a
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 12 deletions.
38 changes: 34 additions & 4 deletions backend/src/BuiltinExecution/Libs/Dict.fs
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,43 @@ let fns : List<BuiltInFn> =
Param.make "val" varA "" ]
returnType = (TDict(TVariable "a"))
description =
"Returns a copy of <param dict> with the <param key> set to <param val>"
"Returns a copy of <param dict> with the <param key> set to <param val>.
If the key already exists in the Dict, an exception is raised."
fn =
(function
| _, vm, _, [ DDict(vt, o); DString k; v ] ->
// CLEANUP perf
Map.foldBack (fun key value acc -> (key, value) :: acc) o [ (k, v) ]
|> TypeChecker.DvalCreator.dict vm.threadID vt
TypeChecker.DvalCreator.dictAddEntry
vm.threadID
vt
o
(k, v)
TypeChecker.ThrowIfDuplicate
|> Ply
| _ -> incorrectArgs ())
sqlSpec = NotYetImplemented
previewable = Pure
deprecated = NotDeprecated }


{ name = fn "dictSetOverridingDuplicates" 0
typeParams = []
parameters =
[ Param.make "dict" (TDict(TVariable "a")) ""
Param.make "key" TString ""
Param.make "val" varA "" ]
returnType = (TDict(TVariable "a"))
description =
"Returns a copy of <param dict> with the <param key> set to <param val>.
If the key already exists in the Dict, the previous value is overwritten."
fn =
(function
| _, vm, _, [ DDict(vt, o); DString k; v ] ->
TypeChecker.DvalCreator.dictAddEntry
vm.threadID
vt
o
(k, v)
TypeChecker.ReplaceValue
|> Ply
| _ -> incorrectArgs ())
sqlSpec = NotYetImplemented
Expand Down
28 changes: 27 additions & 1 deletion backend/src/LibExecution/TypeChecker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ module RTE = RuntimeError
type TypeCheckPathPart = RuntimeError.TypeChecking.TypeCheckPathPart
type ReverseTypeCheckPath = RuntimeError.TypeChecking.ReverseTypeCheckPath

/// Indicates what action to take when key is already in dictionary
type OverwriteBehaviour =
| ReplaceValue
| ThrowIfDuplicate

let rec unifyValueType
(types : Types)
(tst : TypeSymbolTable)
Expand Down Expand Up @@ -351,7 +356,28 @@ module DvalCreator =

DDict(typ, entries)


let dictAddEntry
(threadID : ThreadID)
(typ : ValueType)
(entries : DvalMap)
(newEntry : string * Dval)
(overwrite : OverwriteBehaviour)
: Dval =
let (k, v) = newEntry
match overwrite with
| ThrowIfDuplicate when Map.containsKey k entries ->
RTE.Dicts.Error.TriedToAddKeyAfterAlreadyPresent k
|> RTE.Error.Dict
|> raiseRTE threadID
| ReplaceValue
| ThrowIfDuplicate ->
let vt = Dval.toValueType v
match VT.merge typ vt with
| Ok merged -> DDict(merged, Map.add k v entries)
| Error() ->
RTE.Dicts.Error.TriedToAddMismatchedData(k, typ, vt, v)
|> RTE.Error.Dict
|> raiseRTE threadID

let optionNone (innerType : ValueType) : Dval =
DEnum(Dval.optionType, Dval.optionType, [ innerType ], "None", [])
Expand Down
18 changes: 15 additions & 3 deletions backend/testfiles/execution/stdlib/dict.dark
Original file line number Diff line number Diff line change
Expand Up @@ -147,24 +147,36 @@ module Set =
Stdlib.Dict.set_v0 (Dict { key1 = "val1before" }) "key1" "val1after" =
Builtin.testDerrorMessage "Cannot add two dictionary entries with the same key `key1`"

Stdlib.Dict.set_v0 (Dict { key1 = "val1"; key2 = "val2" }) "key2" "newVal2" =
Builtin.testDerrorMessage "Cannot add two dictionary entries with the same key `key2`"

Dict { key1 = "val1"; key2 = 2L } =
Builtin.testDerrorMessage "Cannot add an Int64 (2) to a dict of String. Failed at key `key2`."

Stdlib.Dict.set_v0 (Dict { key1 = "val1" }) "key2" 2L =
Builtin.testDerrorMessage "PACKAGE.Darklang.Stdlib.Dict.set's 3rd parameter `val` expects String, but got Int64 (2)"

Stdlib.Dict.set_v0 (Dict { }) "key1" "val1" =
(Dict { key1 = "val1" })

Stdlib.Dict.set_v0 (Dict { }) "key1" 2L =
(Dict { key1 = 2L })


module SetOverridingDuplicates =
Stdlib.Dict.setOverridingDuplicates (Dict { key1 = "val1" }) "key2" "val2" =
Stdlib.Dict.setOverridingDuplicates_v0 (Dict { key1 = "val1" }) "key2" "val2" =
(Dict { key1 = "val1"; key2 = "val2" })

Stdlib.Dict.setOverridingDuplicates (Dict { key1 = "val1" }) "key2" 2L =
Stdlib.Dict.setOverridingDuplicates_v0 (Dict { key1 = "val1" }) "key2" 2L =
Builtin.testDerrorMessage "PACKAGE.Darklang.Stdlib.Dict.setOverridingDuplicates's 3rd parameter `val` expects String, but got Int64 (2)"

// (where this differs from .set)
Stdlib.Dict.setOverridingDuplicates (Dict { key1 = "val1before" }) "key1" "val1after" =
Stdlib.Dict.setOverridingDuplicates_v0 (Dict { key1 = "val1before" }) "key1" "val1after" =
(Dict { key1 = "val1after" })

Stdlib.Dict.setOverridingDuplicates_v0 (Dict { key1 = "val1"; key2 = "val2" }) "key2" "newVal2" =
(Dict { key1 = "val1"; key2 = "newVal2" })


module Singleton =
Stdlib.Dict.singleton_v0 "one" 1L = (Dict { one = 1L })
Expand Down
4 changes: 1 addition & 3 deletions packages/darklang/stdlib/dict.dark
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ module Darklang =

/// Returns a copy of <param dict> with the <param key> set to <param val>
let setOverridingDuplicates (dict: Dict<'a>) (key: String) (``val``: 'a) : Dict<'a> =
dict
|> remove key
|> set key ``val``
Builtin.dictSetOverridingDuplicates_v0 dict key ``val``


/// If the <param dict> contains <param key>, returns a copy of <param dict> with <param key> and its associated value removed. Otherwise, returns <param dict> unchanged.
Expand Down
2 changes: 1 addition & 1 deletion scripts/build/_build-server
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,4 @@ def main():
signal.pause()


main()
main()

0 comments on commit db9298a

Please sign in to comment.