diff --git a/packages/components/src/components/FunctionChart1Dist.tsx b/packages/components/src/components/FunctionChart1Dist.tsx index 650d2753..f8d072d7 100644 --- a/packages/components/src/components/FunctionChart1Dist.tsx +++ b/packages/components/src/components/FunctionChart1Dist.tsx @@ -88,7 +88,7 @@ let getPercentiles = ({ chartSettings, fn, environment }) => { let chartPointsData: point[] = chartPointsToRender.map((x) => { let result = runForeign(fn, [x], environment); if (result.tag === "Ok") { - if (result.value.tag == "distribution") { + if (result.value.tag === "distribution") { return { x, value: { tag: "Ok", value: result.value.value } }; } else { return { @@ -165,12 +165,14 @@ export const FunctionChart1Dist: React.FC = ({ setMouseOverlay(NaN); } const signalListeners = { mousemove: handleHover, mouseout: handleOut }; + + //TODO: This custom error handling is a bit hacky and should be improved. let mouseItem: result = !!mouseOverlay ? runForeign(fn, [mouseOverlay], environment) : { tag: "Error", value: { - tag: "REExpectedType", + tag: "RETodo", value: "Hover x-coordinate returned NaN. Expected a number.", }, }; diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_type_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_type_test.res index ea18effc..4f7dabdc 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_type_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_Parse_type_test.res @@ -20,7 +20,7 @@ describe("Peggy parse type", () => { "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", ) }) - describe("high priority modifier", () => { + describe("high priority contract", () => { testParse( "answer: number<-min<-max(100)|string", "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", @@ -30,7 +30,7 @@ describe("Peggy parse type", () => { "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", ) }) - describe("low priority modifier", () => { + describe("low priority contract", () => { testParse( "answer: number | string $ opaque", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", @@ -63,14 +63,14 @@ describe("Peggy parse type", () => { "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", ) }) - describe("type paranthesis", () => { - //$ is introduced to avoid paranthesis + describe("type parenthesis", () => { + //$ is introduced to avoid parenthesis testParse( "answer: (number|string)<-opaque", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", ) }) - describe("squiggle expressions in type modifiers", () => { + describe("squiggle expressions in type contracts", () => { testParse( "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res index c7b9995d..b8a81f25 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_test.res @@ -3,11 +3,10 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue open Jest open Reducer_Peggy_TestHelpers -open Expect describe("Peggy to Expression", () => { describe("literals operators parenthesis", () => { - // Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement + // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement testToExpression("1", "{1}", ~v="1", ()) testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) testToExpression("true", "{true}", ~v="true", ()) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_type_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_type_test.res index 12d2d62b..9849adb0 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_type_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Peggy/Reducer_Peggy_ToExpression_type_test.res @@ -40,7 +40,7 @@ describe("Peggy Types to Expression", () => { (), ) }) - describe("high priority modifier", () => { + describe("high priority contract", () => { testToExpression( "answer: number<-min(1)<-max(100)|string", "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", @@ -78,7 +78,7 @@ describe("Peggy Types to Expression", () => { (), ) }) - describe("low priority modifier", () => { + describe("low priority contract", () => { testToExpression( "answer: number | string $ opaque", "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", @@ -86,7 +86,7 @@ describe("Peggy Types to Expression", () => { (), ) }) - describe("squiggle expressions in type modifiers", () => { + describe("squiggle expressions in type contracts", () => { testToExpression( "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_Compile_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_Compile_test.res new file mode 100644 index 00000000..71f90373 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_Compile_test.res @@ -0,0 +1,52 @@ +module Expression = Reducer_Expression +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module Bindings = Reducer_Bindings +module T = Reducer_Type_T +module TypeCompile = Reducer_Type_Compile + +open Jest +open Expect + +let myIevEval = (aTypeSourceCode: string) => + TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression) +let myIevEvalToString = (aTypeSourceCode: string) => + myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult + +let myIevExpectEqual = (aTypeSourceCode, answer) => + expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer) + +let myIevTest = (test, aTypeSourceCode, answer) => + test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) + +let myTypeEval = (aTypeSourceCode: string) => + TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression) +let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult + +let myTypeExpectEqual = (aTypeSourceCode, answer) => + expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer) + +let myTypeTest = (test, aTypeSourceCode, answer) => + test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer)) + +// | ItTypeIdentifier(string) +myTypeTest(test, "number", "number") +myTypeTest(test, "(number)", "number") +// | ItModifiedType({modifiedType: iType}) +myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})") +myTypeTest(test, "number<-min(0)", "number<-min(0)") +// | ItTypeOr({typeOr: array}) +myTypeTest(test, "number | string", "(number | string)") +// | ItTypeFunction({inputs: array, output: iType}) +myTypeTest(test, "number => number => number", "(number => number => number)") +// | ItTypeArray({element: iType}) +myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})") +myTypeTest(test, "[number]", "[number]") +// | ItTypeTuple({elements: array}) +myTypeTest(test, "[number, string]", "[number, string]") +// | ItTypeRecord({properties: Belt.Map.String.t}) +myIevTest( + test, + "{age: number, name: string}", + "Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})", +) +myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}") diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_arguments_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_arguments_test.res new file mode 100644 index 00000000..07da15bb --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_arguments_test.res @@ -0,0 +1,41 @@ +module Expression = Reducer_Expression +module ExpressionT = Reducer_Expression_T +module ErrorValue = Reducer_ErrorValue +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module Bindings = Reducer_Bindings +module T = Reducer_Type_T +module TypeChecker = Reducer_Type_TypeChecker + +open Jest +open Expect + +let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result< + 'v, + ErrorValue.t, +> => { + let reducerFn = Expression.reduceExpression + let rResult = + Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => + reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) + ) + rResult->Belt.Result.flatMap(result => + switch result { + | IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn) + | _ => Js.Exn.raiseError("Arguments has to be an array") + } + ) +} + +let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string => + switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) { + | Ok(_) => "Ok" + | Error(error) => ErrorValue.errorToString(error) + } + +let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) => + expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer) + +let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) => + test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer)) + +myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok") diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_test.res new file mode 100644 index 00000000..efd9bb18 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_Type/Reducer_Type_TypeChecker_test.res @@ -0,0 +1,70 @@ +module Expression = Reducer_Expression +module ExpressionT = Reducer_Expression_T +module ErrorValue = Reducer_ErrorValue +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module Bindings = Reducer_Bindings +module T = Reducer_Type_T +module TypeChecker = Reducer_Type_TypeChecker + +open Jest +open Expect + +// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn). +// isTypeOfSourceCode is written to use strings instead of expression values. + +let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result< + 'v, + ErrorValue.t, +> => { + let reducerFn = Expression.reduceExpression + let rResult = + Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => + reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) + ) + rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) +} + +let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string => + switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) { + | Ok(_) => "Ok" + | Error(error) => ErrorValue.errorToString(error) + } + +let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) => + expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer) + +let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) => + test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer)) + +myTypeCheckTest(test, "number", "1", "Ok") +myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'") +myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3") +myTypeCheckTest(test, "string", "'a'", "Ok") +myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok") +myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'") +myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'") +myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok") +myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2") +myTypeCheckTest( + test, + "[number, string, string]", + "[1,'a']", + "Expected type: [number, string, string] but got: [1,'a']", +) +myTypeCheckTest( + test, + "[number, string]", + "[1,'a', 3]", + "Expected type: [number, string] but got: [1,'a',3]", +) +myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok") +myTypeCheckTest( + test, + "{age: number, name: string}", + "{age: 1, name: 'a', job: 'IT'}", + "Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}", +) +myTypeCheckTest(test, "number | string", "1", "Ok") +myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1") +myTypeCheckTest(test, "number<-min(10)", "10", "Ok") +myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0") diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_ternaryOperator_test.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_ternaryOperator_test.res index 25353e89..22fa8328 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_ternaryOperator_test.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_ternaryOperator_test.res @@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => { testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')") testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')") testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')") - testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean)") + testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean but got: )") }) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Bindings/Reducer_Bindings.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Bindings/Reducer_Bindings.res index f02cd54f..28175d7a 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Bindings/Reducer_Bindings.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Bindings/Reducer_Bindings.res @@ -1,3 +1,6 @@ +// Only Bindings as the global module is supported +// Other module operations such as import export will be prepreocessed jobs + module ExpressionT = Reducer_Expression_T module InternalExpressionValue = ReducerInterface_InternalExpressionValue open Reducer_ErrorValue diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res index ebdbdf99..0cefe5ee 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltIn.res @@ -10,8 +10,8 @@ open ReducerInterface_InternalExpressionValue open Reducer_ErrorValue /* - MathJs provides default implementations for builtins - This is where all the expected builtins like + = * / sin cos log ln etc are handled + MathJs provides default implementations for built-ins + This is where all the expected built-ins like + = * / sin cos log ln etc are handled DO NOT try to add external function mapping here! */ @@ -219,7 +219,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce | ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr) | ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems) | ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem) - | ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs) + | ("$_typeRecord_$", [IEvRecord(propertyMap)]) => TypeBuilder.typeRecord(propertyMap) | ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) => doAddArray(aValueArray, bValueArray) | ("concat", [IEvString(aValueString), IEvString(bValueString)]) => @@ -277,11 +277,10 @@ let dispatch = (call: functionCall, environment, reducer: ExpressionT.reducerFn) errorValue, > => try { - let callInternalWithReducer = (call, environment) => callInternal(call, environment, reducer) let (fn, args) = call // There is a bug that prevents string match in patterns // So we have to recreate a copy of the string - ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternalWithReducer) + ExternalLibrary.dispatch((Js.String.make(fn), args), environment, reducer, callInternal) } catch { | Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error | _ => RETodo("unhandled rescript exception")->Error diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res index 2df0e83f..c4c76ce3 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Dispatch/Reducer_Dispatch_BuiltInMacros.res @@ -144,7 +144,7 @@ let dispatchMacroCall = ( let ifTrueBlock = eBlock(list{ifTrue}) ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok } - | _ => REExpectedType("Boolean")->Error + | _ => REExpectedType("Boolean", "")->Error } ) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index beaee7f7..211c9f53 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -7,7 +7,7 @@ type errorValue = | REArrayIndexNotFound(string, int) | REAssignmentExpected | REDistributionError(DistributionTypes.error) - | REExpectedType(string) + | REExpectedType(string, string) | REExpressionExpected | REFunctionExpected(string) | REFunctionNotFound(string) @@ -55,6 +55,6 @@ let errorToString = err => | RESymbolNotFound(symbolName) => `${symbolName} is not defined` | RESyntaxError(desc, _) => `Syntax Error: ${desc}` | RETodo(msg) => `TODO: ${msg}` - | REExpectedType(typeName) => `Expected type: ${typeName}` + | REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}` | REUnitNotFound(unitName) => `Unit not found: ${unitName}` } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res index d7ca335c..14db0843 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Exception.res @@ -1,3 +1,3 @@ -// There are switch stament cases in the code which are impossible to reach by design. +// There are switch statement cases in the code which are impossible to reach by design. // ImpossibleException is a sign of programming error. -exception ImpossibleException +exception ImpossibleException(string) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res index bf4d0170..c9739be3 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Expression/Reducer_Expression_T.res @@ -30,12 +30,12 @@ let rec toString = expression => switch expression { | EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) => `{${Belt.List.map(statements, aValue => toString(aValue)) - ->Extra.List.interperse("; ") + ->Extra.List.intersperse("; ") ->Belt.List.toArray ->Js.String.concatMany("")}}` | EList(aList) => `(${Belt.List.map(aList, aValue => toString(aValue)) - ->Extra.List.interperse(" ") + ->Extra.List.intersperse(" ") ->Belt.List.toArray ->Js.String.concatMany("")})` | EValue(aValue) => InternalExpressionValue.toString(aValue) diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_Array.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_Array.res index 58dd4ffd..8c9f2fbb 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_Array.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_Array.res @@ -3,5 +3,5 @@ */ module ExtraList = Reducer_Extra_List -let interperse = (anArray, seperator) => - anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray +let intersperse = (anArray, seperator) => + anArray->Belt.List.fromArray->ExtraList.intersperse(seperator)->Belt.List.toArray diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_List.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_List.res index 9b3bcc3d..b723cca4 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_List.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Extra/Reducer_Extra_List.res @@ -1,9 +1,9 @@ /* Insert seperator between the elements of a list */ -let rec interperse = (aList, seperator) => +let rec intersperse = (aList, seperator) => switch aList { | list{} => list{} | list{a} => list{a} - | list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)} + | list{a, ...rest} => list{a, seperator, ...intersperse(rest, seperator)} } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res index 67873c61..a38c66e9 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.res @@ -91,7 +91,7 @@ let rec pgToString = (peggyNode: peggyNode): string => { args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString let nodesToStringUsingSeparator = (nodes: array, separator: string): string => - nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("") + nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("") switch peggyNode { | PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}" diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res new file mode 100644 index 00000000..2119ee62 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Compile.res @@ -0,0 +1,40 @@ +module ErrorValue = Reducer_ErrorValue +module ExpressionT = Reducer_Expression_T +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module Bindings = Reducer_Bindings +module T = Reducer_Type_T + +let ievFromTypeExpression = ( + typeExpressionSourceCode: string, + reducerFn: ExpressionT.reducerFn, +): result => { + let sIndex = "compiled" + let sourceCode = `type ${sIndex}=${typeExpressionSourceCode}` + Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr => { + let rContext = reducerFn( + expr, + Bindings.emptyBindings, + InternalExpressionValue.defaultEnvironment, + ) + Belt.Result.map(rContext, context => + switch context { + | IEvBindings(nameSpace) => + switch Bindings.getType(nameSpace, sIndex) { + | Some(value) => value + | None => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-none")) + } + | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-raise")) + } + ) + }) +} + +let fromTypeExpression = ( + typeExpressionSourceCode: string, + reducerFn: ExpressionT.reducerFn, +): result => { + ievFromTypeExpression( + (typeExpressionSourceCode: string), + (reducerFn: ExpressionT.reducerFn), + )->Belt.Result.map(T.fromIEvValue) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Contracts.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Contracts.res new file mode 100644 index 00000000..7b68f178 --- /dev/null +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_Contracts.res @@ -0,0 +1,53 @@ +module InternalExpressionValue = ReducerInterface_InternalExpressionValue +module T = Reducer_Type_T + +let isMin = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => { + let pair = (modifierArg, aValue) + switch pair { + | (IEvNumber(a), IEvNumber(b)) => a <= b + | _ => false + } +} + +let isMax = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => { + let pair = (modifierArg, aValue) + switch pair { + | (IEvNumber(a), IEvNumber(b)) => a >= b + | _ => false + } +} + +let isMemberOf = ( + modifierArg: InternalExpressionValue.t, + aValue: InternalExpressionValue.t, +): bool => { + let pair = (modifierArg, aValue) + switch pair { + | (ievA, IEvArray(b)) => Js.Array2.includes(b, ievA) + | _ => false + } +} + +let checkModifier = ( + key: string, + modifierArg: InternalExpressionValue.t, + aValue: InternalExpressionValue.t, +): bool => + switch key { + | "min" => isMin(modifierArg, aValue) + | "max" => isMax(modifierArg, aValue) + | "isMemberOf" => isMemberOf(modifierArg, aValue) + | _ => false + } + +let checkModifiers = ( + contracts: Belt.Map.String.t, + aValue: InternalExpressionValue.t, +): bool => { + contracts->Belt.Map.String.reduce(true, (acc, key, modifierArg) => + switch acc { + | true => checkModifier(key, modifierArg, aValue) + | _ => acc + } + ) +} diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_T.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_T.res index 9b847ca7..511fe815 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_T.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_T.res @@ -3,13 +3,42 @@ open InternalExpressionValue type rec iType = | ItTypeIdentifier(string) - | ItModifiedType({modifiedType: iType}) + | ItModifiedType({modifiedType: iType, contracts: Belt.Map.String.t}) | ItTypeOr({typeOr: array}) | ItTypeFunction({inputs: array, output: iType}) | ItTypeArray({element: iType}) | ItTypeTuple({elements: array}) | ItTypeRecord({properties: Belt.Map.String.t}) +type t = iType +type typeErrorValue = TypeMismatch(t, InternalExpressionValue.t) + +let rec toString = (t: t): string => { + switch t { + | ItTypeIdentifier(s) => s + | ItModifiedType({modifiedType, contracts}) => + `${toString(modifiedType)}${contracts->Belt.Map.String.reduce("", (acc, k, v) => + Js.String2.concatMany(acc, ["<-", k, "(", InternalExpressionValue.toString(v), ")"]) + )}` + | ItTypeOr({typeOr}) => `(${Js.Array2.map(typeOr, toString)->Js.Array2.joinWith(" | ")})` + | ItTypeFunction({inputs, output}) => + `(${inputs->Js.Array2.map(toString)->Js.Array2.joinWith(" => ")} => ${toString(output)})` + | ItTypeArray({element}) => `[${toString(element)}]` + | ItTypeTuple({elements}) => `[${Js.Array2.map(elements, toString)->Js.Array2.joinWith(", ")}]` + | ItTypeRecord({properties}) => + `{${properties + ->Belt.Map.String.toArray + ->Js.Array2.map(((k, v)) => Js.String2.concatMany(k, [": ", toString(v)])) + ->Js.Array2.joinWith(", ")}}` + } +} + +let toStringResult = (rt: result) => + switch rt { + | Ok(t) => toString(t) + | Error(e) => ErrorValue.errorToString(e) + } + let rec fromTypeMap = typeMap => { let default = IEvString("") let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault( @@ -52,31 +81,39 @@ let rec fromTypeMap = typeMap => { "properties", default, ) - //TODO: map type modifiers - switch evTypeTag { - | IEvString("typeIdentifier") => ItModifiedType({modifiedType: fromIEvValue(evTypeIdentifier)}) + + let contracts = + typeMap->Belt.Map.String.keep((k, _v) => ["min", "max", "memberOf"]->Js.Array2.includes(k)) + + let makeIt = switch evTypeTag { + | IEvString("typeIdentifier") => fromIEvValue(evTypeIdentifier) | IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)}) | IEvString("typeFunction") => ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)}) | IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)}) | IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)}) | IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)}) - | _ => raise(Reducer_Exception.ImpossibleException) + | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-evTypeTag")) } + + Belt.Map.String.isEmpty(contracts) + ? makeIt + : ItModifiedType({modifiedType: makeIt, contracts: contracts}) } -and fromIEvValue = (ievValue: InternalExpressionValue.t) => + +and fromIEvValue = (ievValue: InternalExpressionValue.t): iType => switch ievValue { | IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier}) | IEvType(typeMap) => fromTypeMap(typeMap) - | _ => raise(Reducer_Exception.ImpossibleException) + | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievValue")) } and fromIEvArray = (ievArray: InternalExpressionValue.t) => switch ievArray { | IEvArray(array) => array->Belt.Array.map(fromIEvValue) - | _ => raise(Reducer_Exception.ImpossibleException) + | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievArray")) } and fromIEvRecord = (ievRecord: InternalExpressionValue.t) => switch ievRecord { | IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue) - | _ => raise(Reducer_Exception.ImpossibleException) + | _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievRecord")) } diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeBuilder.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeBuilder.res index e51f901a..d3906a38 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeBuilder.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeBuilder.res @@ -56,7 +56,7 @@ let typeFunction = anArray => { let typeArray = element => { let newRecord = Belt.Map.String.fromArray([ - ("typeTag", IEvString("typeTuple")), + ("typeTag", IEvString("typeArray")), ("element", element), ]) newRecord->IEvType->Ok @@ -64,22 +64,14 @@ let typeArray = element => { let typeTuple = anArray => { let newRecord = Belt.Map.String.fromArray([ - ("typeTag", IEvString("typeArray")), + ("typeTag", IEvString("typeTuple")), ("elements", IEvArray(anArray)), ]) newRecord->IEvType->Ok } -let typeRecord = arrayOfPairs => { - let newProperties = - Belt.Array.map(arrayOfPairs, pairValue => - switch pairValue { - | IEvArray([IEvString(key), valueValue]) => (key, valueValue) - | _ => ("wrong key type", pairValue->toStringWithType->IEvString) - } - ) - ->Belt.Map.String.fromArray - ->IEvRecord +let typeRecord = propertyMap => { + let newProperties = propertyMap->IEvRecord let newRecord = Belt.Map.String.fromArray([ ("typeTag", IEvString("typeRecord")), ("properties", newProperties), diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res index 18cb804a..e4336df5 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_Type/Reducer_Type_TypeChecker.res @@ -1,81 +1,168 @@ module ExpressionT = Reducer_Expression_T module InternalExpressionValue = ReducerInterface_InternalExpressionValue module T = Reducer_Type_T -module TypeBuilder = Reducer_Type_TypeBuilder +module TypeContracts = Reducer_Type_Contracts open InternalExpressionValue -type typeErrorValue = - | TypeError(T.iType, InternalExpressionValue.t) - | TypeErrorWithPosition(T.iType, InternalExpressionValue.t, int) - | TypeErrorWithProperty(T.iType, InternalExpressionValue.t, string) - -let rec isOfResolvedIType = (anIType: T.iType, aValue): result => { +let rec isITypeOf = (anIType: T.iType, aValue): result => { let caseTypeIdentifier = (anUpperTypeName, aValue) => { let aTypeName = anUpperTypeName->Js.String2.toLowerCase let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase - switch aTypeName === valueTypeName { + switch aTypeName == valueTypeName { | true => Ok(true) - | false => TypeError(anIType, aValue)->Error + | false => T.TypeMismatch(anIType, aValue)->Error } } - let _caseRecord = (anIType, evValue, propertyMap, map) => { - Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => { + let caseRecord = (anIType, propertyMap: Belt.Map.String.t, evValue) => + switch evValue { + | IEvRecord(aRecord) => + if ( + Js.Array2.length(propertyMap->Belt.Map.String.keysToArray) == + Js.Array2.length(aRecord->Belt.Map.String.keysToArray) + ) { + Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => { + Belt.Result.flatMap(acc, _ => + switch Belt.Map.String.get(aRecord, property) { + | Some(propertyValue) => isITypeOf(propertyType, propertyValue) + | None => T.TypeMismatch(anIType, evValue)->Error + } + ) + }) + } else { + T.TypeMismatch(anIType, evValue)->Error + } + + | _ => T.TypeMismatch(anIType, evValue)->Error + } + + let caseArray = (anIType, elementType, evValue) => + switch evValue { + | IEvArray(anArray) => + Belt.Array.reduce(anArray, Ok(true), (acc, element) => + Belt.Result.flatMap(acc, _ => + switch isITypeOf(elementType, element) { + | Ok(_) => Ok(true) + | Error(error) => error->Error + } + ) + ) + | _ => T.TypeMismatch(anIType, evValue)->Error + } + + let caseTuple = (anIType, elementTypes, evValue) => + switch evValue { + | IEvArray(anArray) => + if Js.Array2.length(elementTypes) == Js.Array2.length(anArray) { + let zipped = Belt.Array.zip(elementTypes, anArray) + Belt.Array.reduce(zipped, Ok(true), (acc, (elementType, element)) => + switch acc { + | Ok(_) => + switch isITypeOf(elementType, element) { + | Ok(_) => acc + | Error(error) => Error(error) + } + | _ => acc + } + ) + } else { + T.TypeMismatch(anIType, evValue)->Error + } + | _ => T.TypeMismatch(anIType, evValue)->Error + } + + let caseOr = (anIType, anITypeArray, evValue) => + switch Belt.Array.reduce(anITypeArray, Ok(false), (acc, anIType) => Belt.Result.flatMap(acc, _ => - switch Belt.Map.String.get(map, property) { - | Some(propertyValue) => isOfResolvedIType(propertyType, propertyValue) - | None => TypeErrorWithProperty(anIType, evValue, property)->Error + switch acc { + | Ok(false) => + switch isITypeOf(anIType, evValue) { + | Ok(_) => Ok(true) + | Error(_) => acc + } + | _ => acc } ) - }) - } - let _caseArray = (anIType, evValue, elementType, anArray) => { - Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => { - switch isOfResolvedIType(elementType, element) { - | Ok(_) => acc - | Error(_) => TypeErrorWithPosition(anIType, evValue, index)->Error + ) { + | Ok(true) => Ok(true) + | Ok(false) => T.TypeMismatch(anIType, evValue)->Error + | Error(error) => Error(error) + } + + let caseModifiedType = ( + anIType: T.iType, + modifiedType: T.iType, + contracts: Belt.Map.String.t, + aValue: InternalExpressionValue.t, + ) => { + isITypeOf(modifiedType, aValue)->Belt.Result.flatMap(_result => { + if TypeContracts.checkModifiers(contracts, aValue) { + Ok(true) + } else { + T.TypeMismatch(anIType, aValue)->Error } }) } switch anIType { | ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue) - // TODO: Work in progress. Code is commented to make an a release of other features - // | ItModifiedType({modifiedType: anIType}) => raise(Reducer_Exception.ImpossibleException) - // | ItTypeOr({typeOr: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) - // | ItTypeFunction({inputs: anITypeArray, output: anIType}) => - // raise(Reducer_Exception.ImpossibleException) - // | ItTypeArray({element: anIType}) => raise(Reducer_Exception.ImpossibleException) - // | ItTypeTuple({elements: anITypeArray}) => raise(Reducer_Exception.ImpossibleException) - // | ItTypeRecord({properties: anITypeMap}) => raise(Reducer_Exception.ImpossibleException) - | _ => raise(Reducer_Exception.ImpossibleException) + | ItModifiedType({modifiedType, contracts}) => + caseModifiedType(anIType, modifiedType, contracts, aValue) //{modifiedType: iType, contracts: Belt.Map.String.t} + | ItTypeOr({typeOr}) => caseOr(anIType, typeOr, aValue) + | ItTypeFunction(_) => + raise( + Reducer_Exception.ImpossibleException( + "Reducer_TypeChecker-functions are without a type at the moment", + ), + ) + | ItTypeArray({element}) => caseArray(anIType, element, aValue) + | ItTypeTuple({elements}) => caseTuple(anIType, elements, aValue) + | ItTypeRecord({properties}) => caseRecord(anIType, properties, aValue) } } -let isOfResolvedType = (aType: InternalExpressionValue.t, aValue): result => - aType->T.fromIEvValue->isOfResolvedIType(aValue) +let isTypeOf = ( + typeExpressionSourceCode: string, + aValue: InternalExpressionValue.t, + reducerFn: ExpressionT.reducerFn, +): result => { + switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) { + | Ok(anIType) => + switch isITypeOf(anIType, aValue) { + | Ok(_) => Ok(aValue) + | Error(T.TypeMismatch(anIType, evValue)) => + Error( + ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString), + ) + } + | Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch + } +} -// TODO: Work in progress. Code is commented to make an a release of other features -// let checkArguments = ( -// evFunctionType: InternalExpressionValue.t, -// args: array, -// ) => { -// let functionType = switch evFunctionType { -// | IEvRecord(functionType) => functionType -// | _ => raise(Reducer_Exception.ImpossibleException) -// } -// let evInputs = functionType->Belt.Map.String.getWithDefault("inputs", []->IEvArray) -// let inputs = switch evInputs { -// | IEvArray(inputs) => inputs -// | _ => raise(Reducer_Exception.ImpossibleException) -// } -// let rTupleType = TypeBuilder.typeTuple(inputs) -// Belt.Result.flatMap(rTupleType, tuppleType => isOfResolvedType(tuppleType, args->IEvArray)) -// } +let checkITypeArguments = (anIType: T.iType, args: array): result< + bool, + T.typeErrorValue, +> => { + switch anIType { + | T.ItTypeFunction({inputs}) => isITypeOf(T.ItTypeTuple({elements: inputs}), args->IEvArray) + | _ => T.TypeMismatch(anIType, args->IEvArray)->Error + } +} -// let compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => { -// statement = `type compiled=${typeExpression}` - -// } - -//TODO: asGuard +let checkArguments = ( + typeExpressionSourceCode: string, + args: array, + reducerFn: ExpressionT.reducerFn, +): result => { + switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) { + | Ok(anIType) => + switch checkITypeArguments(anIType, args) { + | Ok(_) => Ok(args->IEvArray) + | Error(T.TypeMismatch(anIType, evValue)) => + Error( + ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString), + ) + } + | Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch + } +} diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res index 3e6172c9..2a479bb5 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExternalLibrary.res @@ -23,7 +23,7 @@ let tryRegistry = ((fnName, args): InternalExpressionValue.functionCall, env) => ) } -let dispatch = (call: InternalExpressionValue.functionCall, environment, chain): result< +let dispatch = (call: InternalExpressionValue.functionCall, environment, reducer, chain): result< internalExpressionValue, 'e, > => { @@ -33,7 +33,7 @@ let dispatch = (call: InternalExpressionValue.functionCall, environment, chain): () => ReducerInterface_Duration.dispatch(call, environment), () => ReducerInterface_Number.dispatch(call, environment), () => tryRegistry(call, environment), - ])->E.O2.default(chain(call, environment)) + ])->E.O2.default(chain(call, environment, reducer)) } /* If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally. diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_InternalExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_InternalExpressionValue.res index 06ab2376..4523b02a 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_InternalExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_InternalExpressionValue.res @@ -84,7 +84,7 @@ let toStringWithType = aValue => | IEvDeclaration(_) => `Declaration::${toString(aValue)}` | IEvDistribution(_) => `Distribution::${toString(aValue)}` | IEvLambda(_) => `Lambda::${toString(aValue)}` - | IEvBindings(_) => `Module::${toString(aValue)}` + | IEvBindings(_) => `Bindings::${toString(aValue)}` | IEvNumber(_) => `Number::${toString(aValue)}` | IEvRecord(_) => `Record::${toString(aValue)}` | IEvString(_) => `String::${toString(aValue)}`