diff --git a/backend/src/Builtins/Builtins.Http.Client/Libs/HttpClient.fs b/backend/src/Builtins/Builtins.Http.Client/Libs/HttpClient.fs index d2329019d3..bfeda38d06 100644 --- a/backend/src/Builtins/Builtins.Http.Client/Libs/HttpClient.fs +++ b/backend/src/Builtins/Builtins.Http.Client/Libs/HttpClient.fs @@ -689,9 +689,10 @@ let fns (config : Configuration) : List = FQFnName.fqPackage ( PackageRefs.Fn.Stdlib.HttpClient.request () ), + [], 2, "headers", - VT.list (VT.tuple VT.string VT.string []), + headersType, Dval.toValueType notAPair, notAPair ) @@ -821,9 +822,10 @@ let fns (config : Configuration) : List = FQFnName.fqPackage ( PackageRefs.Fn.Stdlib.HttpClient.stream () ), + [], 2, "headers", - VT.list (VT.tuple VT.string VT.string []), + headersType, Dval.toValueType notAPair, notAPair ) diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Float.fs b/backend/src/Builtins/Builtins.Pure/Libs/Float.fs index d54f3be012..d479b9196f 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/Float.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/Float.fs @@ -297,9 +297,9 @@ let fns () : List = TypeReference.result TFloat (TCustomType( - { originalName = [] - resolved = - Ok(FQTypeName.fqPackage (PackageRefs.Type.Stdlib.floatParseError ())) }, + NameResolution.ok ( + FQTypeName.fqPackage (PackageRefs.Type.Stdlib.floatParseError ()) + ), [] )) description = diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Int128.fs b/backend/src/Builtins/Builtins.Pure/Libs/Int128.fs index d396451e9d..3efc272c6b 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/Int128.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/Int128.fs @@ -290,11 +290,9 @@ let fns () : List = TypeReference.result TInt128 (TCustomType( - { originalName = [] - resolved = - Ok( - FQTypeName.fqPackage (PackageRefs.Type.Stdlib.int128ParseError ()) - ) }, + NameResolution.ok ( + FQTypeName.fqPackage (PackageRefs.Type.Stdlib.int128ParseError ()) + ), [] )) description = "Returns the value of a " diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Json.fs b/backend/src/Builtins/Builtins.Pure/Libs/Json.fs index d777f81f45..39c066a01c 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/Json.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/Json.fs @@ -574,7 +574,9 @@ let parse |> Ply.List.flatten |> Ply.map (TypeChecker.DvalCreator.dict threadID VT.unknownTODO) - | TCustomType({ resolved = Ok typeName }, typeArgs), jsonValueKind -> + | TCustomType(({ resolved = Ok typeName } as nr), typeArgs), jsonValueKind -> + // The written reference name, so diagnostics don't show a hash-sibling. + let typeReferenceName = nr.originalName uply { let! typeArgsVT = typeArgs |> Ply.List.mapSequentially (TypeReference.toVT types tst) @@ -664,6 +666,7 @@ let parse threadID tst typeName + typeReferenceName typeArgsVT caseName fields @@ -722,6 +725,7 @@ let parse threadID tst typeName + typeReferenceName typeArgsVT fields return record diff --git a/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs b/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs index 460cafba83..5ca671008c 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs @@ -10,6 +10,48 @@ module ValueType = LibExecution.ValueType module RTE = RuntimeError +/// Removes diagnostic names from resolved custom type refs before equality. +/// Unresolved refs keep their name because it is the only identity they have. +let rec private normalizeTypeRefForEquality (t : TypeReference) : TypeReference = + let r = normalizeTypeRefForEquality + match t with + | TUnit + | TBool + | TInt8 + | TUInt8 + | TInt16 + | TUInt16 + | TInt32 + | TUInt32 + | TInt64 + | TUInt64 + | TInt128 + | TUInt128 + | TInt + | TFloat + | TChar + | TString + | TUuid + | TDateTime + | TBlob + | TVariable _ -> t + | TList inner -> TList(r inner) + | TDict inner -> TDict(r inner) + | TStream inner -> TStream(r inner) + | TDB inner -> TDB(r inner) + | TTuple(first, second, theRest) -> TTuple(r first, r second, List.map r theRest) + | TFn(args, ret) -> TFn(NEList.map r args, r ret) + | TCustomType(nr, typeArgs) -> + let nr = + match nr.resolved with + | Ok _ -> { nr with originalName = [] } + | Error _ -> nr + TCustomType(nr, List.map r typeArgs) + +let private typeReferenceEquals (a : TypeReference) (b : TypeReference) : bool = + normalizeTypeRefForEquality a = normalizeTypeRefForEquality b + + /// Structural equality. Walks two Dvals in parallel and returns /// true iff every reachable leaf compares equal. Type errors /// (callers passing structurally-incompatible Dvals) return false @@ -107,11 +149,17 @@ let rec equals (a : Dval) (b : Dval) : bool = | DApplicable a, DApplicable b -> match a, b with - // CLEANUP exprId is a partial check — fully checking LambdaImpl - // equality needs lambda-internal-state work. Today this is - // "same-source-position lambdas compare equal." + // CLEANUP: lambda equality only checks source position. Include captures, + // applied args, and type bindings if lambda equality semantics are tightened. | AppLambda a, AppLambda b -> a.exprId = b.exprId - | AppNamedFn a, AppNamedFn b -> a = b + | AppNamedFn a, AppNamedFn b -> + // Reference names are diagnostic-only, so they do not affect equality. + a.name = b.name + && List.length a.typeArgs = List.length b.typeArgs + && List.forall2 typeReferenceEquals a.typeArgs b.typeArgs + && List.length a.argsSoFar = List.length b.argsSoFar + && List.forall2 r a.argsSoFar b.argsSoFar + && a.typeSymbolTable = b.typeSymbolTable | _ -> false | DDB a, DDB b -> a = b diff --git a/backend/src/Builtins/Builtins.Pure/Libs/UInt128.fs b/backend/src/Builtins/Builtins.Pure/Libs/UInt128.fs index a83d8478f8..bd9281a100 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/UInt128.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/UInt128.fs @@ -237,11 +237,9 @@ let fns () : List = TypeReference.result TUInt128 (TCustomType( - { originalName = [] - resolved = - Ok( - FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uint128ParseError ()) - ) }, + NameResolution.ok ( + FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uint128ParseError ()) + ), [] )) description = "Returns the value of a " diff --git a/backend/src/Builtins/Builtins.Pure/Libs/UInt64.fs b/backend/src/Builtins/Builtins.Pure/Libs/UInt64.fs index 4fb8c9a79f..8dba22074d 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/UInt64.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/UInt64.fs @@ -243,11 +243,9 @@ let fns () : List = TypeReference.result TUInt64 (TCustomType( - { originalName = [] - resolved = - Ok( - FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uint64ParseError ()) - ) }, + NameResolution.ok ( + FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uint64ParseError ()) + ), [] )) description = "Returns the value of a " diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Uuid.fs b/backend/src/Builtins/Builtins.Pure/Libs/Uuid.fs index b6709833e6..f6036aca1a 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/Uuid.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/Uuid.fs @@ -28,9 +28,9 @@ let fns () : List = TypeReference.result TUuid (TCustomType( - { originalName = [] - resolved = - Ok(FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uuidParseError ())) }, + NameResolution.ok ( + FQTypeName.fqPackage (PackageRefs.Type.Stdlib.uuidParseError ()) + ), [] )) description = diff --git a/backend/src/LibExecution/Execution.fs b/backend/src/LibExecution/Execution.fs index cdd87e7181..db1ad15cf1 100644 --- a/backend/src/LibExecution/Execution.fs +++ b/backend/src/LibExecution/Execution.fs @@ -180,9 +180,10 @@ let executeApplicable } -let executeFunction +let executeReferencedFunction (exeState : RT.ExecutionState) (name : RT.FQFnName.FQFnName) + (referenceName : List) (typeArgs : List) (args : NEList) : Task = @@ -191,6 +192,7 @@ let executeFunction let fnInstr, fnReg, rc = let namedFn : RT.ApplicableNamedFn = { name = name + referenceName = referenceName typeSymbolTable = Map.empty typeArgs = typeArgs argsSoFar = [] } @@ -214,6 +216,18 @@ let executeFunction executeExpr exeState instrs +/// Execute a function by its resolved/content-addressed name only. +/// Use `executeReferencedFunction` when the caller has a user-written +/// reference name to preserve in runtime errors. +let executeFunction + (exeState : RT.ExecutionState) + (name : RT.FQFnName.FQFnName) + (typeArgs : List) + (args : NEList) + : Task = + executeReferencedFunction exeState name [] typeArgs args + + let runtimeErrorToString (state : RT.ExecutionState) (rte : RT.RuntimeError.Error) diff --git a/backend/src/LibExecution/Interpreter.fs b/backend/src/LibExecution/Interpreter.fs index 8ef35c23b0..9cf70037c8 100644 --- a/backend/src/LibExecution/Interpreter.fs +++ b/backend/src/LibExecution/Interpreter.fs @@ -302,7 +302,7 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply // we should error in some better way (CLEANUP) // , but the point is that callstacks shouldn't be created for builtin fn calls - raiseRTE (RTE.FnNotFound(FQFnName.fqBuiltin "builtin" 0)) + raiseRTE (RTE.FnNotFound(FQFnName.fqBuiltin "builtin" 0, [])) | Function(FQFnName.Package fn) -> uply { @@ -317,7 +317,11 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply return raiseRTE (RTE.FnNotFound(FQFnName.Package fn)) + | None -> + return + raiseRTE ( + RTE.FnNotFound(FQFnName.Package fn, currentFrame.fnReferenceName) + ) } @@ -331,7 +335,22 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply registers[reg] <- value + | LoadVal(reg, value) -> + // ESelf is compiled without a reference name because hashes can have + // multiple names. Attach the current calling name before it escapes. + let value = + match value with + | DApplicable(AppNamedFn fn) when + List.isEmpty fn.referenceName + && not (List.isEmpty currentFrame.fnReferenceName) + && Some fn.name = ExecutionPoint.enclosingFn + currentFrame.executionPoint + -> + DApplicable( + AppNamedFn { fn with referenceName = currentFrame.fnReferenceName } + ) + | _ -> value + registers[reg] <- value | CopyVal(copyTo, copyFrom) -> registers[copyTo] <- registers[copyFrom] @@ -473,7 +492,7 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply + | CreateRecord(recordReg, sourceTypeName, typeReferenceName, typeArgs, fields) -> let fields = fields |> List.map (fun (name, valueReg) -> (name, registers[valueReg])) @@ -489,6 +508,7 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply + | CreateEnum(enumReg, typeName, typeReferenceName, typeArgs, caseName, fields) -> let fields = fields |> List.map (fun valueReg -> registers[valueReg]) let tst = currentFrame.typeSymbolTable @@ -558,6 +578,7 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply RTE.Apply |> raiseRTE let handleWrongTypeArgCount expected actual = - RTE.Applications.WrongNumberOfTypeArgsForFn(name, expected, actual) + RTE.Applications.WrongNumberOfTypeArgsForFn( + name, + applicable.referenceName, + expected, + actual + ) |> RTE.Apply |> raiseRTE let typeCheckParam = - TypeChecker.checkFnParam exeState.types applicable.name + TypeChecker.checkFnParam + exeState.types + applicable.name + applicable.referenceName let mutable tst = if Map.isEmpty applicable.typeSymbolTable then @@ -753,7 +789,10 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply match Map.find builtin exeState.fns.builtIn with - | None -> return RTE.FnNotFound(FQFnName.Builtin builtin) |> raiseRTE + | None -> + return + RTE.FnNotFound(FQFnName.Builtin builtin, applicable.referenceName) + |> raiseRTE | Some fn -> // Step 1: resolve typeArgs against the OUTER tst so the // wrapper-pass-through pattern works (a wrapper body @@ -893,6 +932,7 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply raiseRTE match! exeState.fns.package pkg with - | None -> return RTE.FnNotFound(FQFnName.Package pkg) |> raiseRTE + | None -> + return + RTE.FnNotFound(FQFnName.Package pkg, applicable.referenceName) + |> raiseRTE | Some fn -> // Step 1: resolve any explicit typeArgs against the // OUTER tst — they may reference outer-scope TVariables @@ -1062,7 +1105,8 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply List.iteri (fun i arg -> r[i] <- arg) r typeSymbolTable = frameTst - executionPoint = pkgEp } + executionPoint = pkgEp + fnReferenceName = applicable.referenceName } |> Some | RaiseNRE(names, nre) -> raiseRTE (RTE.ParseTimeNameResolution(names, nre)) @@ -1114,7 +1158,10 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply return RTE.FnNotFound fnName |> raiseRTE + | None -> + return + RTE.FnNotFound(fnName, currentFrame.fnReferenceName) + |> raiseRTE | Some fn -> return fn.returnType } @@ -1131,12 +1178,13 @@ let rec private executeInner (exeState : ExecutionState) (vm : VMState) : Ply - let! expectedVT = - TypeReference.toVT exeState.types tst expectedReturnType + let expectedTypeRef = + TypeReference.resolveTypeVariables tst expectedReturnType return RuntimeError.Applications.FnResultNotExpectedType( fnName, - expectedVT, + currentFrame.fnReferenceName, + expectedTypeRef, Dval.toValueType resultOfFrame, resultOfFrame ) diff --git a/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs b/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs index 7e6a62ba07..da10d54525 100644 --- a/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs +++ b/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs @@ -54,18 +54,29 @@ module NameResolutionError = | PT.NameResolutionError.NotFound -> RT.NameResolutionError.NotFound | PT.NameResolutionError.InvalidName -> RT.NameResolutionError.InvalidName +// Unqualified package references can be ambiguous when multiple package items +// share a hash. Use the resolved fully-qualified package name for diagnostics, +// while preserving qualified references as written. +let private resolvedOriginalName + (originalName : List) + (location : Option) + : List = + match originalName, location with + | ([] | [ _ ]), Some l -> [ l.owner ] @ l.modules @ [ l.name ] + | originalName, _ -> originalName + module NameResolution = - // `location` is package-store metadata — load-bearing for propagation - // rewrites (AstTransformer's byLocation substitution), SCC hash - // substitution (Canonical writer), dep-edge inserts, and deferred - // refresh. The runtime executes against already-resolved hashes and - // has no use for any of that, so we drop location at the PT→RT boundary. + // `location` is PT metadata used for package rewrites, hashing, dependency + // tracking, and refresh. RT does not carry it, but PT->RT may use it to derive + // diagnostic reference names before dropping it. let toRT (f : 'a -> 'b) (nr : PT.NameResolution<'a>) : RT.NameResolution<'b> = - { originalName = nr.originalName - resolved = - match nr.resolved with - | Ok resolved -> Ok(f resolved.name) - | Error e -> Error(NameResolutionError.toRT e) } + match nr.resolved with + | Ok resolved -> + { originalName = resolvedOriginalName nr.originalName resolved.location + resolved = Ok(f resolved.name) } + | Error e -> + { originalName = nr.originalName + resolved = Error(NameResolutionError.toRT e) } module TypeReference = @@ -750,6 +761,7 @@ module Expr = // Create a named function reference to the current function let namedFn : RT.ApplicableNamedFn = { name = FQFnName.toRT fnName + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -884,6 +896,7 @@ module Expr = right.registerCount, RT.AppNamedFn { name = InfixFnName.toFnName infix |> RT.FQFnName.Builtin + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -922,9 +935,11 @@ module Expr = // functions - | PT.EFnName(_, { resolved = Ok resolved }) -> + | PT.EFnName(_, ({ resolved = Ok resolved } as nameResolution)) -> let namedFn : RT.ApplicableNamedFn = { name = FQFnName.toRT resolved.name + referenceName = + resolvedOriginalName nameResolution.originalName resolved.location typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1082,7 +1097,10 @@ module Expr = instructions = [ RT.RaiseNRE(names, NameResolutionError.toRT nre) ] resultIn = returnReg } - | PT.ERecord(_id, { resolved = Ok { name = typeName } }, typeArgs, fields) -> + | PT.ERecord(_id, + ({ resolved = Ok resolved } as nameResolution), + typeArgs, + fields) -> let recordReg, rc = rc, rc + 1 // CLEANUP: complain if there are no fields @@ -1103,7 +1121,8 @@ module Expr = instrs @ [ RT.CreateRecord( recordReg, - FQTypeName.toRT typeName, + FQTypeName.toRT resolved.name, + resolvedOriginalName nameResolution.originalName resolved.location, List.map TypeReference.toRT typeArgs, fields ) ] @@ -1153,7 +1172,11 @@ module Expr = instructions = [ RT.RaiseNRE(names, NameResolutionError.toRT nre) ] resultIn = returnReg } - | PT.EEnum(_id, { resolved = Ok { name = typeName } }, typeArgs, caseName, fields) -> + | PT.EEnum(_id, + ({ resolved = Ok resolved } as nameResolution), + typeArgs, + caseName, + fields) -> let enumReg, rc = rc, rc + 1 let (rcAfterFields, instrs, fields) = @@ -1171,7 +1194,8 @@ module Expr = instrs @ [ RT.CreateEnum( enumReg, - FQTypeName.toRT typeName, + FQTypeName.toRT resolved.name, + resolvedOriginalName nameResolution.originalName resolved.location, List.map TypeReference.toRT typeArgs, caseName, fields diff --git a/backend/src/LibExecution/RTQueryCompiler.fs b/backend/src/LibExecution/RTQueryCompiler.fs index 7e83ca8931..190da97eeb 100644 --- a/backend/src/LibExecution/RTQueryCompiler.fs +++ b/backend/src/LibExecution/RTQueryCompiler.fs @@ -100,6 +100,7 @@ let partialEvaluate // Load the function reference let appFn : RT.ApplicableNamedFn = { name = fnName + referenceName = [] typeSymbolTable = Map.empty typeArgs = typeArgs argsSoFar = [] } @@ -483,7 +484,7 @@ and executeInstruction let listDval = RT.DList(RT.ValueType.Unknown, items) Ok(state.withReg (createTo, Literal listDval)) | RT.CreateDict _ -> Error "Dict creation not supported in SQL queries" - | RT.CreateRecord(createTo, typeName, _typeArgs, fields) -> + | RT.CreateRecord(createTo, typeName, _typeReferenceName, _typeArgs, fields) -> // Build a record Dval from the field values (must all be literals) let rec buildFields acc remaining = match remaining with @@ -501,7 +502,12 @@ and executeInstruction let recordDval = RT.DRecord(typeName, typeName, [], fieldMap) Ok(state.withReg (createTo, Literal recordDval)) - | RT.CreateEnum(createTo, typeName, _typeArgs, caseName, fieldRegs) -> + | RT.CreateEnum(createTo, + typeName, + _typeReferenceName, + _typeArgs, + caseName, + fieldRegs) -> // Build an enum Dval from the field values (must all be literals) let rec buildFields acc remaining = match remaining with diff --git a/backend/src/LibExecution/RuntimeTypes.fs b/backend/src/LibExecution/RuntimeTypes.fs index 4c8e42c1fe..2b58e092c5 100644 --- a/backend/src/LibExecution/RuntimeTypes.fs +++ b/backend/src/LibExecution/RuntimeTypes.fs @@ -597,6 +597,7 @@ type Instruction = | CreateRecord of createTo : Register * typeName : FQTypeName.FQTypeName * + typeReferenceName : List * typeArgs : List * fields : List @@ -614,6 +615,7 @@ type Instruction = | CreateEnum of createTo : Register * typeName : FQTypeName.FQTypeName * + typeReferenceName : List * typeArgs : List * caseName : string * fields : List @@ -700,14 +702,19 @@ and LambdaImpl = and ApplicableNamedFn = - { name : FQFnName.FQFnName + { + name : FQFnName.FQFnName + + /// Used only to render diagnostics; not part of function identity. + referenceName : List typeSymbolTable : TypeSymbolTable // CLEANUP maybe this could be List? typeArgs : List - argsSoFar : List } + argsSoFar : List + } and ApplicableLambda = { @@ -1049,18 +1056,22 @@ module RuntimeError = type Error = | ConstructionWrongNumberOfFields of typeName : FQTypeName.FQTypeName * + typeReferenceName : List * caseName : string * expectedFieldCount : int * actualFieldCount : int | ConstructionCaseNotFound of typeName : FQTypeName.FQTypeName * + typeReferenceName : List * caseName : string | ConstructionFieldOfWrongType of caseName : string * fieldIndex : int * - expectedType : ValueType * + // Declaration-shaped expected type for diagnostics. Inferred vars are + // resolved; aliases and custom refs keep their names. + expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval @@ -1072,14 +1083,17 @@ module RuntimeError = type Error = // -- Creation -- - | CreationTypeNotRecord of name : FQTypeName.FQTypeName + | CreationTypeNotRecord of + name : FQTypeName.FQTypeName * + typeReferenceName : List | CreationEmptyKey // I'm not quite sure how this can be reached(?) | CreationMissingField of fieldName : string | CreationDuplicateField of fieldName : string | CreationFieldNotExpected of fieldName : string | CreationFieldOfWrongType of fieldName : string * - expectedType : ValueType * + // Declaration-shaped expected type for diagnostics. + expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval @@ -1090,7 +1104,8 @@ module RuntimeError = | UpdateFieldNotExpected of fieldName : string | UpdateFieldOfWrongType of fieldName : string * - expectedType : ValueType * + // Declaration-shaped expected type for diagnostics. + expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval @@ -1108,24 +1123,34 @@ module RuntimeError = // specific to fns | WrongNumberOfTypeArgsForFn of fn : FQFnName.FQFnName * + fnReferenceName : List * expected : int * actual : int | CannotApplyTypeArgsMoreThanOnce - | TooManyArgsForFn of fn : FQFnName.FQFnName * expected : int * actual : int + | TooManyArgsForFn of + fn : FQFnName.FQFnName * + fnReferenceName : List * + expected : int * + actual : int | FnParameterNotExpectedType of fnName : FQFnName.FQFnName * + fnReferenceName : List * paramIndex : int * paramName : string * - expectedType : ValueType * + // Declaration-shaped expected type for diagnostics. Inferred vars are + // resolved; aliases and custom refs keep their names. + expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval | FnResultNotExpectedType of fnName : FQFnName.FQFnName * - expectedType : ValueType * + fnReferenceName : List * + // Declaration-shaped expected type for diagnostics. + expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval @@ -1189,7 +1214,7 @@ module RuntimeError = | ParseTimeNameResolution of originalName : List * NameResolutionError | TypeNotFound of name : FQTypeName.FQTypeName - | FnNotFound of name : FQFnName.FQFnName + | FnNotFound of name : FQFnName.FQFnName * referenceName : List | ValueNotFound of name : FQValueName.FQValueName /// Raised when calling a package fn whose hash is currently marked @@ -1267,6 +1292,14 @@ type ExecutionPoint = /// Executing some lambda | Lambda of parent : ExecutionPoint * lambdaExprId : id +module ExecutionPoint = + /// The function whose body is executing, following through lambdas. + let rec enclosingFn (ep : ExecutionPoint) : Option = + match ep with + | Function fn -> Some fn + | Lambda(parent, _) -> enclosingFn parent + | Source -> None + /// Not: in reverse order type CallStack = List @@ -1778,6 +1811,9 @@ type CallFrame = // so we keep only one copy of such, in the root of the VMState executionPoint : ExecutionPoint + /// Calling name used for diagnostics and ESelf. + fnReferenceName : List + /// What instruction index we are currently 'at' mutable programCounter : int @@ -1893,6 +1929,7 @@ type VMState = let rootCallFrame : CallFrame = { id = rootCallFrameID executionPoint = Source + fnReferenceName = [] programCounter = 0 registers = Array.zeroCreate instrs.registerCount typeSymbolTable = Map.empty @@ -2277,6 +2314,13 @@ module TypeReference = | KTFn(args, ret) -> TFn(NEList.map fromVT args, fromVT ret) | KTDB inner -> TDB(fromVT inner) + /// Convert a ValueType back to a TypeReference, for diagnostics where only + /// a runtime-inferred type is available. Unknown becomes `TVariable "_"`. + let fromValueType (vt : ValueType) : TypeReference = + match vt with + | ValueType.Unknown -> TVariable "_" + | ValueType.Known kt -> fromKnownType kt + /// Resolve type variables in a TypeReference using the TypeSymbolTable. /// If a variable is not found or resolves to Unknown, it is kept as TVariable. diff --git a/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs b/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs index a205ca5395..a3db43b09a 100644 --- a/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs +++ b/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs @@ -178,6 +178,15 @@ module NameResolutionError = | _ -> Exception.raiseInternal "Invalid NameResolutionError" [] +// Reference names are diagnostic-only name segments, represented as a +// plain List on both sides. +module ReferenceName = + let toDT (referenceName : List) : Dval = + DList(VT.string, referenceName |> List.map DString) + + let fromDT (d : Dval) : List = d |> D.list D.string + + module NameResolution = let typeName () = FQTypeName.fqPackage ( @@ -569,6 +578,7 @@ module ApplicableNamedFn = let fields = [ "name", FQFnName.toDT namedFn.name + "referenceName", ReferenceName.toDT namedFn.referenceName "typeArgs", DList( VT.known (TypeReference.knownType ()), @@ -583,6 +593,7 @@ module ApplicableNamedFn = match d with | DRecord(_, _, _, fields) -> { name = FQFnName.fromDT (fields |> D.field "name") + referenceName = fields |> D.field "referenceName" |> ReferenceName.fromDT typeSymbolTable = Map.empty // TODO typeArgs = fields |> D.field "typeArgs" |> D.list TypeReference.fromDT argsSoFar = fields |> D.field "argsSoFar" |> D.list Dval.fromDT } @@ -1053,8 +1064,9 @@ module RuntimeError = let (caseName, fields) = match e with - | RuntimeError.Records.CreationTypeNotRecord name -> - "CreationTypeNotRecord", [ FQTypeName.toDT name ] + | RuntimeError.Records.CreationTypeNotRecord(name, typeReferenceName) -> + "CreationTypeNotRecord", + [ FQTypeName.toDT name; ReferenceName.toDT typeReferenceName ] | RuntimeError.Records.CreationEmptyKey -> "CreationEmptyKey", [] | RuntimeError.Records.CreationMissingField fieldName -> "CreationMissingField", [ DString fieldName ] @@ -1063,12 +1075,12 @@ module RuntimeError = | RuntimeError.Records.CreationFieldNotExpected fieldName -> "CreationFieldNotExpected", [ DString fieldName ] | RuntimeError.Records.CreationFieldOfWrongType(fieldName, - expectedType, + expectedTypeRef, actualType, actual) -> "CreationFieldOfWrongType", [ DString fieldName - ValueType.toDT expectedType + TypeReference.toDT expectedTypeRef ValueType.toDT actualType Dval.toDT actual ] @@ -1080,12 +1092,12 @@ module RuntimeError = | RuntimeError.Records.UpdateFieldNotExpected fieldName -> "UpdateFieldNotExpected", [ DString fieldName ] | RuntimeError.Records.UpdateFieldOfWrongType(fieldName, - expectedType, + expectedTypeRef, actualType, actual) -> "UpdateFieldOfWrongType", [ DString fieldName - ValueType.toDT expectedType + TypeReference.toDT expectedTypeRef ValueType.toDT actualType Dval.toDT actual ] @@ -1100,8 +1112,11 @@ module RuntimeError = let fromDT (d : Dval) : RuntimeError.Records.Error = match d with - | DEnum(_, _, [], "CreationTypeNotRecord", [ name ]) -> - RuntimeError.Records.CreationTypeNotRecord(FQTypeName.fromDT name) + | DEnum(_, _, [], "CreationTypeNotRecord", [ name; typeReferenceName ]) -> + RuntimeError.Records.CreationTypeNotRecord( + FQTypeName.fromDT name, + ReferenceName.fromDT typeReferenceName + ) | DEnum(_, _, [], "CreationEmptyKey", []) -> RuntimeError.Records.CreationEmptyKey | DEnum(_, _, [], "CreationMissingField", [ fieldName ]) -> @@ -1114,10 +1129,10 @@ module RuntimeError = _, [], "CreationFieldOfWrongType", - [ fieldName; expectedType; actualType; actual ]) -> + [ fieldName; expectedTypeRef; actualType; actual ]) -> RuntimeError.Records.CreationFieldOfWrongType( D.string fieldName, - ValueType.fromDT expectedType, + TypeReference.fromDT expectedTypeRef, ValueType.fromDT actualType, Dval.fromDT actual ) @@ -1133,10 +1148,10 @@ module RuntimeError = _, [], "UpdateFieldOfWrongType", - [ fieldName; expectedType; actualType; actual ]) -> + [ fieldName; expectedTypeRef; actualType; actual ]) -> RuntimeError.Records.UpdateFieldOfWrongType( D.string fieldName, - ValueType.fromDT expectedType, + TypeReference.fromDT expectedTypeRef, ValueType.fromDT actualType, Dval.fromDT actual ) @@ -1159,25 +1174,32 @@ module RuntimeError = let (caseName, fields) = match e with | RuntimeError.Enums.ConstructionWrongNumberOfFields(typeName, + typeReferenceName, caseName, expectedFieldCount, actualFieldCount) -> "ConstructionWrongNumberOfFields", [ FQTypeName.toDT typeName + ReferenceName.toDT typeReferenceName DString caseName dintOfInt expectedFieldCount dintOfInt actualFieldCount ] - | RuntimeError.Enums.ConstructionCaseNotFound(typeName, caseName) -> - "ConstructionCaseNotFound", [ FQTypeName.toDT typeName; DString caseName ] + | RuntimeError.Enums.ConstructionCaseNotFound(typeName, + typeReferenceName, + caseName) -> + "ConstructionCaseNotFound", + [ FQTypeName.toDT typeName + ReferenceName.toDT typeReferenceName + DString caseName ] | RuntimeError.Enums.ConstructionFieldOfWrongType(caseName, fieldIndex, - expectedType, + expectedTypeRef, actualType, actualValue) -> "ConstructionFieldOfWrongType", [ DString caseName dintOfInt fieldIndex - ValueType.toDT expectedType + TypeReference.toDT expectedTypeRef ValueType.toDT actualType Dval.toDT actualValue ] @@ -1189,27 +1211,37 @@ module RuntimeError = _, [], "ConstructionWrongNumberOfFields", - [ typeName; caseName; expectedFieldCount; actualFieldCount ]) -> + [ typeName + typeReferenceName + caseName + expectedFieldCount + actualFieldCount ]) -> RuntimeError.Enums.ConstructionWrongNumberOfFields( FQTypeName.fromDT typeName, + ReferenceName.fromDT typeReferenceName, D.string caseName, D.int expectedFieldCount, D.int actualFieldCount ) - | DEnum(_, _, [], "ConstructionCaseNotFound", [ typeName; caseName ]) -> + | DEnum(_, + _, + [], + "ConstructionCaseNotFound", + [ typeName; typeReferenceName; caseName ]) -> RuntimeError.Enums.ConstructionCaseNotFound( FQTypeName.fromDT typeName, + ReferenceName.fromDT typeReferenceName, D.string caseName ) | DEnum(_, _, [], "ConstructionFieldOfWrongType", - [ caseName; fieldIndex; expectedType; actualType; actualValue ]) -> + [ caseName; fieldIndex; expectedTypeRef; actualType; actualValue ]) -> RuntimeError.Enums.ConstructionFieldOfWrongType( D.string caseName, D.int fieldIndex, - ValueType.fromDT expectedType, + TypeReference.fromDT expectedTypeRef, ValueType.fromDT actualType, Dval.fromDT actualValue ) @@ -1228,34 +1260,50 @@ module RuntimeError = "ExpectedApplicableButNot", [ ValueType.toDT actualTyp; Dval.toDT actualValue ] - | RuntimeError.Applications.WrongNumberOfTypeArgsForFn(fn, expected, actual) -> + | RuntimeError.Applications.WrongNumberOfTypeArgsForFn(fn, + fnReferenceName, + expected, + actual) -> "WrongNumberOfTypeArgsForFn", - [ FQFnName.toDT fn; dintOfInt expected; dintOfInt actual ] + [ FQFnName.toDT fn + ReferenceName.toDT fnReferenceName + dintOfInt expected + dintOfInt actual ] | RuntimeError.Applications.CannotApplyTypeArgsMoreThanOnce -> "CannotApplyTypeArgsMoreThanOnce", [] - | RuntimeError.Applications.TooManyArgsForFn(fn, expected, actual) -> + | RuntimeError.Applications.TooManyArgsForFn(fn, + fnReferenceName, + expected, + actual) -> "TooManyArgsForFn", - [ FQFnName.toDT fn; dintOfInt expected; dintOfInt actual ] + [ FQFnName.toDT fn + ReferenceName.toDT fnReferenceName + dintOfInt expected + dintOfInt actual ] | RuntimeError.Applications.FnParameterNotExpectedType(fnName, + fnReferenceName, paramIndex, paramName, - expectedType, + expectedTypeRef, actualType, actualValue) -> "FnParameterNotExpectedType", [ FQFnName.toDT fnName + ReferenceName.toDT fnReferenceName dintOfInt paramIndex DString paramName - ValueType.toDT expectedType + TypeReference.toDT expectedTypeRef ValueType.toDT actualType Dval.toDT actualValue ] | RuntimeError.Applications.FnResultNotExpectedType(fnName, - expectedType, + fnReferenceName, + expectedTypeRef, actualType, actualValue) -> "FnResultNotExpectedType", [ FQFnName.toDT fnName - ValueType.toDT expectedType + ReferenceName.toDT fnReferenceName + TypeReference.toDT expectedTypeRef ValueType.toDT actualType Dval.toDT actualValue ] @@ -1279,17 +1327,23 @@ module RuntimeError = Dval.fromDT actualValue ) - | DEnum(_, _, [], "WrongNumberOfTypeArgsForFn", [ fn; expected; actual ]) -> + | DEnum(_, + _, + [], + "WrongNumberOfTypeArgsForFn", + [ fn; fnReferenceName; expected; actual ]) -> RuntimeError.Applications.WrongNumberOfTypeArgsForFn( FQFnName.fromDT fn, + ReferenceName.fromDT fnReferenceName, D.int expected, D.int actual ) | DEnum(_, _, [], "CannotApplyTypeArgsMoreThanOnce", []) -> RuntimeError.Applications.CannotApplyTypeArgsMoreThanOnce - | DEnum(_, _, [], "TooManyArgsForFn", [ fn; expected; actual ]) -> + | DEnum(_, _, [], "TooManyArgsForFn", [ fn; fnReferenceName; expected; actual ]) -> RuntimeError.Applications.TooManyArgsForFn( FQFnName.fromDT fn, + ReferenceName.fromDT fnReferenceName, D.int expected, D.int actual ) @@ -1297,12 +1351,19 @@ module RuntimeError = _, [], "FnParameterNotExpectedType", - [ fnName; paramIndex; paramName; expectedType; actualType; actualValue ]) -> + [ fnName + fnReferenceName + paramIndex + paramName + expectedTypeRef + actualType + actualValue ]) -> RuntimeError.Applications.FnParameterNotExpectedType( FQFnName.fromDT fnName, + ReferenceName.fromDT fnReferenceName, D.int paramIndex, D.string paramName, - ValueType.fromDT expectedType, + TypeReference.fromDT expectedTypeRef, ValueType.fromDT actualType, Dval.fromDT actualValue ) @@ -1310,10 +1371,11 @@ module RuntimeError = _, [], "FnResultNotExpectedType", - [ fnName; expectedType; actualType; actualValue ]) -> + [ fnName; fnReferenceName; expectedTypeRef; actualType; actualValue ]) -> RuntimeError.Applications.FnResultNotExpectedType( FQFnName.fromDT fnName, - ValueType.fromDT expectedType, + ReferenceName.fromDT fnReferenceName, + TypeReference.fromDT expectedTypeRef, ValueType.fromDT actualType, Dval.fromDT actualValue ) @@ -1474,7 +1536,8 @@ module RuntimeError = [ DList(VT.string, names |> List.map DString); NameResolutionError.toDT e ] | RuntimeError.TypeNotFound name -> "TypeNotFound", [ FQTypeName.toDT name ] | RuntimeError.ValueNotFound name -> "ValueNotFound", [ FQValueName.toDT name ] - | RuntimeError.FnNotFound name -> "FnNotFound", [ FQFnName.toDT name ] + | RuntimeError.FnNotFound(name, referenceName) -> + "FnNotFound", [ FQFnName.toDT name; ReferenceName.toDT referenceName ] | RuntimeError.DeprecatedItemHalted target -> "DeprecatedItemHalted", [ Hash.toDT target ] | RuntimeError.WrongNumberOfTypeArgsForType(fn, expected, actual) -> @@ -1541,8 +1604,11 @@ module RuntimeError = RuntimeError.TypeNotFound(FQTypeName.fromDT name) | DEnum(_, _, [], "ValueNotFound", [ name ]) -> RuntimeError.ValueNotFound(FQValueName.fromDT name) - | DEnum(_, _, [], "FnNotFound", [ name ]) -> - RuntimeError.FnNotFound(FQFnName.fromDT name) + | DEnum(_, _, [], "FnNotFound", [ name; referenceName ]) -> + RuntimeError.FnNotFound( + FQFnName.fromDT name, + ReferenceName.fromDT referenceName + ) | DEnum(_, _, [], "DeprecatedItemHalted", [ target ]) -> RuntimeError.DeprecatedItemHalted(Hash.fromDT target) | DEnum(_, _, [], "WrongNumberOfTypeArgsForType", [ fn; expected; actual ]) -> diff --git a/backend/src/LibExecution/TypeChecker.fs b/backend/src/LibExecution/TypeChecker.fs index 432b3f1887..5df34b33fb 100644 --- a/backend/src/LibExecution/TypeChecker.fs +++ b/backend/src/LibExecution/TypeChecker.fs @@ -245,6 +245,7 @@ let rec resolveType let checkFnParam (types : Types) (fnName : FQFnName.FQFnName) + (fnReferenceName : List) (tst : TypeSymbolTable) (paramIndex : int) (paramName : string) @@ -252,17 +253,19 @@ let checkFnParam (actual : Dval) : Ply> = uply { - let! expected = TypeReference.unwrapAlias types expected - match! unify types tst expected actual with + let! unwrapped = TypeReference.unwrapAlias types expected + match! unify types tst unwrapped actual with | Ok updatedTst -> return Ok updatedTst | Error _path -> - let! expected = TypeReference.toVT types tst expected + // Render the declaration type while resolving any inferred type variables. + let expectedTypeRef = TypeReference.resolveTypeVariables tst expected return RTE.Applications.FnParameterNotExpectedType( fnName, + fnReferenceName, paramIndex, paramName, - expected, + expectedTypeRef, Dval.toValueType actual, actual ) @@ -274,20 +277,23 @@ let checkFnParam let checkFnResult (types : Types) (fnName : FQFnName.FQFnName) + (fnReferenceName : List) (tst : TypeSymbolTable) (expected : TypeReference) (actual : Dval) : Ply> = uply { - let! expected = TypeReference.unwrapAlias types expected - let! expectedVT = TypeReference.toVT types tst expected - match! unify types tst expected actual with + let! unwrapped = TypeReference.unwrapAlias types expected + match! unify types tst unwrapped actual with | Ok updatedTst -> return Ok updatedTst | Error _path -> + // Render the declaration type while resolving any inferred type variables. + let expectedTypeRef = TypeReference.resolveTypeVariables tst expected return RTE.Applications.FnResultNotExpectedType( fnName, - expectedVT, + fnReferenceName, + expectedTypeRef, Dval.toValueType actual, actual ) @@ -396,7 +402,13 @@ module DvalCreator = match VT.merge expected vt with | Ok typ -> DEnum(typeName, typeName, [ typ ], "Some", [ dv ]) | Error() -> - RuntimeError.Enums.ConstructionFieldOfWrongType("Some", 0, expected, vt, dv) + RuntimeError.Enums.ConstructionFieldOfWrongType( + "Some", + 0, + TypeReference.fromValueType expected, + vt, + dv + ) |> RuntimeError.Enum |> raiseRTE threadID @@ -426,7 +438,7 @@ module DvalCreator = RuntimeError.Enums.ConstructionFieldOfWrongType( "Ok", 0, - okType, + TypeReference.fromValueType okType, dvalType, dvOk ) @@ -447,7 +459,7 @@ module DvalCreator = RuntimeError.Enums.ConstructionFieldOfWrongType( "Error", 0, - errorType, + TypeReference.fromValueType errorType, dvalType, dvError ) @@ -494,6 +506,7 @@ module DvalCreator = (threadID : ThreadID) (tst : TypeSymbolTable) (sourceTypeName : FQTypeName.FQTypeName) + (typeReferenceName : List) (typeArgs : List) (caseName : string) (fields : List) @@ -511,7 +524,11 @@ module DvalCreator = match foundCaseDef with | None -> return - RTE.Enums.ConstructionCaseNotFound(resolvedTypeName, caseName) + RTE.Enums.ConstructionCaseNotFound( + resolvedTypeName, + typeReferenceName, + caseName + ) |> RTE.Error.Enum |> raiseRTE threadID @@ -523,6 +540,7 @@ module DvalCreator = if expected <> actual then RTE.Enums.ConstructionWrongNumberOfFields( resolvedTypeName, + typeReferenceName, caseName, expected, actual @@ -537,14 +555,16 @@ module DvalCreator = Ply.List.foldSequentiallyWithIndex (fun fieldIndex (typeArgs, fieldsInReverse, tst) (fieldDef, actualField) -> uply { - let! expected = TypeReference.toVT types tst fieldDef match! unify types tst fieldDef actualField with | Error _path -> + // Render the declaration type while resolving any inferred type variables. + let expectedTypeRef = + TypeReference.resolveTypeVariables tst fieldDef return RTE.Enums.ConstructionFieldOfWrongType( caseName, fieldIndex, - expected, + expectedTypeRef, Dval.toValueType actualField, actualField ) @@ -552,7 +572,8 @@ module DvalCreator = |> raiseRTE threadID | Ok newTST -> - let! expected = TypeReference.toVT types tst fieldDef + let expectedTypeRef = + TypeReference.resolveTypeVariables newTST fieldDef // Update resultant typeArgs based on what we learned from this field // , by checking the TST. let newTypeArgs = @@ -571,7 +592,7 @@ module DvalCreator = RTE.Enums.ConstructionFieldOfWrongType( caseName, fieldIndex, - expected, + expectedTypeRef, Dval.toValueType actualField, actualField ) @@ -594,6 +615,7 @@ module DvalCreator = (types : Types) (threadID : ThreadID) (typeName : FQTypeName.FQTypeName) + (typeReferenceName : List) (typeArgs : List) : Ply * @@ -607,7 +629,7 @@ module DvalCreator = | TypeDeclaration.Record fields -> return (resolvedName, typeArgs, fields) | _ -> return - RTE.Records.CreationTypeNotRecord typeName + RTE.Records.CreationTypeNotRecord(typeName, typeReferenceName) |> RTE.Record |> raiseRTE threadID } @@ -622,12 +644,13 @@ module DvalCreator = (threadID : ThreadID) (tst : TypeSymbolTable) (sourceTypeName : FQTypeName.FQTypeName) + (typeReferenceName : List) (typeArgs : List) (fields : List) : Ply = uply { let! (resolvedTypeName, resolvedTypeArgs, expectedFields) = - resolveRecordType types threadID sourceTypeName typeArgs + resolveRecordType types threadID sourceTypeName typeReferenceName typeArgs let tst = resolvedTypeArgs |> List.fold (fun acc (name, vt) -> Map.add name vt acc) tst @@ -657,13 +680,15 @@ module DvalCreator = |> raiseRTE threadID | Some fieldDef -> - let! expected = TypeReference.toVT types tst fieldDef.typ match! unify types tst fieldDef.typ fieldValue with | Error _path -> + // Render the declaration type while resolving any inferred type variables. + let expectedTypeRef = + TypeReference.resolveTypeVariables tst fieldDef.typ return RTE.Records.CreationFieldOfWrongType( fieldName, - expected, + expectedTypeRef, Dval.toValueType fieldValue, fieldValue ) @@ -671,7 +696,8 @@ module DvalCreator = |> raiseRTE threadID | Ok newTST -> - let! expected = TypeReference.toVT types newTST fieldDef.typ + let expectedTypeRef = + TypeReference.resolveTypeVariables newTST fieldDef.typ // Update resultant typeArgs based on what we learned from this field // , by checking the TST. let newTypeArgs = @@ -689,7 +715,7 @@ module DvalCreator = | Error() -> RTE.Records.CreationFieldOfWrongType( fieldName, - expected, + expectedTypeRef, Dval.toValueType fieldValue, fieldValue ) @@ -739,8 +765,9 @@ module DvalCreator = (fieldUpdates : List) : Ply = uply { + // Updates copy an existing value, so no type name was written here. let! (_resolvedTypeName, resolvedTypeArgs, expectedFields) = - resolveRecordType types threadID sourceTypeName [] + resolveRecordType types threadID sourceTypeName [] [] let resolvedTypeArgs = List.zip typeArgsBeforeUpdate resolvedTypeArgs @@ -766,21 +793,24 @@ module DvalCreator = |> raiseRTE threadID | Some fieldDef -> - let! expected = TypeReference.toVT types tst fieldDef.typ match! unify types tst fieldDef.typ fieldValue with | Error _path -> // CLEANUP involve path, somehow + // Render the declaration type while resolving any inferred type variables. + let expectedTypeRef = + TypeReference.resolveTypeVariables tst fieldDef.typ return RTE.Records.UpdateFieldOfWrongType( fieldName, - expected, + expectedTypeRef, Dval.toValueType fieldValue, fieldValue ) |> RTE.Record |> raiseRTE threadID | Ok updatedTst -> - let! expected = TypeReference.toVT types updatedTst fieldDef.typ + let expectedTypeRef = + TypeReference.resolveTypeVariables updatedTst fieldDef.typ // Update resultant typeArgs based on what we learned from this field // , by checking the TST. @@ -799,7 +829,7 @@ module DvalCreator = | Error() -> RTE.Records.UpdateFieldOfWrongType( fieldName, - expected, + expectedTypeRef, Dval.toValueType fieldValue, fieldValue ) diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/Common.fs b/backend/src/LibSerialization/Binary/Serializers/RT/Common.fs index e6c77e7ce0..a7729694ce 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/Common.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/Common.fs @@ -26,6 +26,14 @@ module NameResolutionError = | b -> raiseFormatError $"Invalid NameResolutionError tag: {b}" +// Reference names are diagnostic-only name segments (a plain string list). +module ReferenceName = + let write (w : BinaryWriter) (referenceName : List) : unit = + List.write w String.write referenceName + + let read (r : BinaryReader) : List = List.read r String.read + + module NameResolution = let write (writeInner : BinaryWriter -> 'inner -> unit) diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs b/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs index c815b0c5c1..626bf642f6 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs @@ -96,6 +96,7 @@ and writeApplicableLambda (w : BinaryWriter) (lambda : ApplicableLambda) = and writeApplicableNamedFn (w : BinaryWriter) (namedFn : ApplicableNamedFn) = FQFnName.write w namedFn.name + ReferenceName.write w namedFn.referenceName writeTypeSymbolTable w namedFn.typeSymbolTable List.write w TypeReference.write namedFn.typeArgs List.write w writeDval namedFn.argsSoFar @@ -280,10 +281,12 @@ and readApplicableLambda (r : BinaryReader) : ApplicableLambda = and readApplicableNamedFn (r : BinaryReader) : ApplicableNamedFn = let name = FQFnName.read r + let referenceName = ReferenceName.read r let typeSymbolTable = readTypeSymbolTable r let typeArgs = List.read r TypeReference.read let argsSoFar = List.read r readDval { name = name + referenceName = referenceName typeSymbolTable = typeSymbolTable typeArgs = typeArgs argsSoFar = argsSoFar } diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs b/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs index 5d8c815060..7bb9139853 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs @@ -265,10 +265,11 @@ module Instruction = String.write w key w.Write(reg : int)) entries - | CreateRecord(createTo, typeName, typeArgs, fields) -> + | CreateRecord(createTo, typeName, typeReferenceName, typeArgs, fields) -> w.Write 13uy w.Write(createTo : int) FQTypeName.write w typeName + ReferenceName.write w typeReferenceName List.write w TypeReference.write typeArgs List.write w @@ -291,10 +292,11 @@ module Instruction = w.Write(targetReg : int) w.Write(recordReg : int) String.write w fieldName - | CreateEnum(createTo, typeName, typeArgs, caseName, fields) -> + | CreateEnum(createTo, typeName, typeReferenceName, typeArgs, caseName, fields) -> w.Write 16uy w.Write(createTo : int) FQTypeName.write w typeName + ReferenceName.write w typeReferenceName List.write w TypeReference.write typeArgs String.write w caseName List.write w (fun w reg -> w.Write(reg : int)) fields @@ -388,13 +390,14 @@ module Instruction = | 13uy -> let createTo = r.ReadInt32() let typeName = FQTypeName.read r + let typeReferenceName = ReferenceName.read r let typeArgs = List.read r TypeReference.read let fields = List.read r (fun r -> let field = String.read r let reg = r.ReadInt32() (field, reg)) - CreateRecord(createTo, typeName, typeArgs, fields) + CreateRecord(createTo, typeName, typeReferenceName, typeArgs, fields) | 14uy -> let createTo = r.ReadInt32() let originalRecordReg = r.ReadInt32() @@ -412,10 +415,11 @@ module Instruction = | 16uy -> let createTo = r.ReadInt32() let typeName = FQTypeName.read r + let typeReferenceName = ReferenceName.read r let typeArgs = List.read r TypeReference.read let caseName = String.read r let fields = List.read r (fun r -> r.ReadInt32()) - CreateEnum(createTo, typeName, typeArgs, caseName, fields) + CreateEnum(createTo, typeName, typeReferenceName, typeArgs, caseName, fields) | 17uy -> let createTo = r.ReadInt32() let valueName = FQValueName.read r diff --git a/backend/src/LibSerialization/DvalReprInternalQueryable.fs b/backend/src/LibSerialization/DvalReprInternalQueryable.fs index 4f18190c08..8c9bd73bf8 100644 --- a/backend/src/LibSerialization/DvalReprInternalQueryable.fs +++ b/backend/src/LibSerialization/DvalReprInternalQueryable.fs @@ -331,6 +331,7 @@ let parseJsonV0 threadID tst typeName + [] VT.typeArgsTODO fields else @@ -371,6 +372,7 @@ let parseJsonV0 tst typeName [] + [] caseName fields return enum diff --git a/backend/testfiles/execution/language/derror.dark b/backend/testfiles/execution/language/derror.dark index d3a3612718..0e9a0a2aae 100644 --- a/backend/testfiles/execution/language/derror.dark +++ b/backend/testfiles/execution/language/derror.dark @@ -3,7 +3,7 @@ module Error = (Stdlib.Option.Option.Some 10L) "not an option" (fun (a, b) -> "1") - ) = error="Darklang.Stdlib.Option.map2's 2nd parameter `option2` expects Darklang.Stdlib.Option.Option<_>, but got String (\"not an option\")" + ) = error="Darklang.Stdlib.Option.map2's 2nd parameter `option2` expects Darklang.Stdlib.Option.Option<'b>, but got String (\"not an option\")" // Check we get previous errors before later ones diff --git a/backend/testfiles/execution/stdlib/dict.dark b/backend/testfiles/execution/stdlib/dict.dark index 57f89f3076..61b13965b4 100644 --- a/backend/testfiles/execution/stdlib/dict.dark +++ b/backend/testfiles/execution/stdlib/dict.dark @@ -50,7 +50,7 @@ module FromListOverwritingDuplicates = // CLEANUP improve the error message to: // In Dict.fromListOverwritingDuplicates's 1st argument (`entries`), the nested value `entries[1]` should be a (String, 'b). However, an Int64 (2) was passed instead. Stdlib.Dict.fromListOverwritingDuplicates [ (1L, 2L) ] = error="Darklang.Stdlib.Dict.fromListOverwritingDuplicates's 1st parameter `entries` expects List<(String * Int64)>, but got List<(Int64 * Int64)> ([(1, 2)])" - Stdlib.Dict.fromListOverwritingDuplicates [ 1L ] = error="Darklang.Stdlib.Dict.fromListOverwritingDuplicates's 1st parameter `entries` expects List<(String * _)>, but got List ([1])" + Stdlib.Dict.fromListOverwritingDuplicates [ 1L ] = error="Darklang.Stdlib.Dict.fromListOverwritingDuplicates's 1st parameter `entries` expects List<(String * 'a)>, but got List ([1])" module FromList = Stdlib.Dict.fromList [] = Stdlib.Option.Option.Some(Dict { }) diff --git a/backend/testfiles/execution/stdlib/list.dark b/backend/testfiles/execution/stdlib/list.dark index bcec03d957..d616782f7f 100644 --- a/backend/testfiles/execution/stdlib/list.dark +++ b/backend/testfiles/execution/stdlib/list.dark @@ -126,7 +126,7 @@ module FindFirstIndex = module Flatten = Stdlib.List.flatten [ [ 1L ]; [ 2L ]; [ 3L ] ] = [ 1L; 2L; 3L ] - Stdlib.List.flatten [ 1l; 2l; 3l ] = error="Darklang.Stdlib.List.flatten's 1st parameter `list` expects List>, but got List ([1, 2, 3])" + Stdlib.List.flatten [ 1l; 2l; 3l ] = error="Darklang.Stdlib.List.flatten's 1st parameter `list` expects List>, but got List ([1, 2, 3])" Stdlib.List.flatten [ [ 1L ]; [ [ 2L; 3L ] ] ] = error="Cannot add a List> ([[2, 3]]) to a list of List. Failed at index 1." Stdlib.List.flatten [ [ [] ] ] = [ [] ] Stdlib.List.flatten [ [] ] = [] diff --git a/backend/tests/Tests/Blob.Tests.fs b/backend/tests/Tests/Blob.Tests.fs index aa3d85cfae..d203efa877 100644 --- a/backend/tests/Tests/Blob.Tests.fs +++ b/backend/tests/Tests/Blob.Tests.fs @@ -683,6 +683,7 @@ let private fakeAppNamedFn (argsSoFar : List) : RT.Dval = RT.DApplicable( RT.AppNamedFn { name = name + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = argsSoFar } diff --git a/backend/tests/Tests/Interpreter.Tests.fs b/backend/tests/Tests/Interpreter.Tests.fs index 0c728eebe2..8c3783cb80 100644 --- a/backend/tests/Tests/Interpreter.Tests.fs +++ b/backend/tests/Tests/Interpreter.Tests.fs @@ -58,6 +58,95 @@ module Basic = let tests = testList "Basic" [ one ] +module TypeMismatchDiagnostics = + let inferredVariablesStayInDeclaredTypeReference = + testTask "mismatch diagnostics resolve variables without losing declared names" { + let typeName = RT.FQTypeName.fqPackage "diagnostic-type" + let declared = + RT.TCustomType( + { originalName = [ "Tests"; "Types"; "Declared" ]; resolved = Ok typeName }, + [] + ) + let expected = RT.TTuple(declared, RT.TVariable "a", []) + let tst = Map [ "a", VT.int64 ] + let! result = + LibExecution.TypeChecker.checkFnParam + RT.Types.empty + (RT.FQFnName.fqBuiltin "diagnostic" 0) + [] + tst + 0 + "value" + expected + (RT.DString "wrong") + |> Ply.toTask + match result with + | Error(RTE.Error.Apply(RTE.Applications.FnParameterNotExpectedType(_, + _, + _, + _, + actualTypeRef, + _, + _))) -> + Expect.equal + actualTypeRef + (RT.TTuple(declared, RT.TInt64, [])) + "the declared custom name is retained while 'a is resolved" + | other -> failtestf "expected parameter mismatch, got %A" other + } + + let aliasNamesStayInDeclaredTypeReference = + testTask "mismatch diagnostics preserve declared alias names" { + let aliasHash = RT.Hash "diagnostic-user-id" + let aliasName = RT.FQTypeName.Package aliasHash + let aliasRef = + RT.TCustomType( + { originalName = [ "Tests"; "Types"; "UserId" ]; resolved = Ok aliasName }, + [] + ) + let types : RT.Types = + { package = + fun requested -> + if requested = aliasHash then + Ply( + Some + { hash = aliasHash + declaration = + { typeParams = [] + definition = RT.TypeDeclaration.Alias RT.TString } } + ) + else + Ply None } + let! result = + LibExecution.TypeChecker.checkFnParam + types + (RT.FQFnName.fqBuiltin "diagnostic" 0) + [] + Map.empty + 0 + "id" + aliasRef + (RT.DInt64 123L) + |> Ply.toTask + match result with + | Error(RTE.Error.Apply(RTE.Applications.FnParameterNotExpectedType(_, + _, + _, + _, + actualTypeRef, + _, + _))) -> + Expect.equal actualTypeRef aliasRef "the declared alias name is retained" + | other -> failtestf "expected parameter mismatch, got %A" other + } + + let tests = + testList + "Type mismatch diagnostics" + [ inferredVariablesStayInDeclaredTypeReference + aliasNamesStayInDeclaredTypeReference ] + + module List = let simple = t @@ -368,7 +457,7 @@ module RecordUpdate = "let r = Test.Test { key = true }\nlet r2 = { r | key = 1 }\nr2.key" E.RecordUpdate.fieldWithWrongType (RTE.Record( - RTE.Records.UpdateFieldOfWrongType("key", VT.bool, VT.int64, RT.DInt64 1L) + RTE.Records.UpdateFieldOfWrongType("key", RT.TBool, VT.int64, RT.DInt64 1L) )) let tests = @@ -589,6 +678,7 @@ module Fns = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -601,6 +691,7 @@ module Fns = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [ RT.DInt64 1 ] } @@ -628,6 +719,7 @@ module Fns = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.MyAdd.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -640,6 +732,7 @@ module Fns = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.MyAdd.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [ RT.DInt64 1 ] } @@ -660,6 +753,7 @@ module Fns = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.Fact.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -700,6 +794,95 @@ module Fns = (RT.DBool true) let tests = testList "Outer" [ applied ] + module ReferenceName = + let private referenceNameFor name : List = [ "Tests"; "Names"; name ] + + let missingFunctionUsesReferencedLocation = + testTask "missing package function retains its referenced name" { + let! state = executionStateFor TestValues.pm false Map.empty + let name = RT.FQFnName.fqPackage "missing-function-for-display-name" + let referenceName = referenceNameFor "Missing" + let! result = + LibExecution.Execution.executeReferencedFunction + state + name + referenceName + [] + (NEList.singleton RT.DUnit) + match result with + | Error(RTE.Error.FnNotFound(actualName, actualReferenceName), _) -> + Expect.equal actualName name "content name" + Expect.equal + actualReferenceName + referenceName + "referenced reference name" + | other -> failtestf "expected FnNotFound, got %A" other + } + + let recursiveSelfUsesCallingLocation = + testTask "recursive self mismatch retains the calling name" { + let! (baseState : RT.ExecutionState) = + executionStateFor TestValues.pm false Map.empty + let hash = RT.Hash "recursive-function-for-display-name" + let name = RT.FQFnName.fqPackage "recursive-function-for-display-name" + let referenceName = referenceNameFor "Recursive" + let self : RT.Dval = + RT.DApplicable( + RT.AppNamedFn + { name = name + referenceName = [] + typeSymbolTable = Map.empty + typeArgs = [] + argsSoFar = [] } + ) + let fn : RT.PackageFn.PackageFn = + { hash = hash + typeParams = [] + parameters = NEList.singleton { name = "value"; typ = RT.TInt64 } + returnType = RT.TInt64 + body = + { registerCount = 4 + instructions = + [ RT.LoadVal(1, self) + RT.LoadVal(2, RT.DString "wrong") + RT.Apply(3, 1, [], NEList.singleton 2) ] + resultIn = 3 } } + let package + (requested : RT.FQFnName.Package) + : Ply> = + if requested = hash then + Ply(Some fn) + else + baseState.fns.package requested + let state : RT.ExecutionState = + { baseState with fns = { baseState.fns with package = package } } + + let! result = + LibExecution.Execution.executeReferencedFunction + state + name + referenceName + [] + (NEList.singleton (RT.DInt64 1L)) + match result with + | Error(RTE.Error.Apply(RTE.Applications.FnParameterNotExpectedType(actualName, + actualReferenceName, + _, + _, + _, + _, + _)), + _) -> + Expect.equal actualName name "recursive function name" + Expect.equal actualReferenceName referenceName "calling reference name" + | other -> failtestf "expected parameter mismatch, got %A" other + } + + let tests = + testList + "Reference names" + [ missingFunctionUsesReferencedLocation; recursiveSelfUsesCallingLocation ] + let tests = testList "Package" @@ -707,7 +890,8 @@ module Fns = Fact.tests Recusrsion.tests MyFnThatTakesALambda.tests - Outer.tests ] + Outer.tests + ReferenceName.tests ] let tests = testList "Fns" [ Builtin.tests; Package.tests ] @@ -784,6 +968,7 @@ let tests = testList "Interpreter" [ Basic.tests + TypeMismatchDiagnostics.tests CapsGate.tests List.tests Let.tests diff --git a/backend/tests/Tests/LibExecution.Tests.fs b/backend/tests/Tests/LibExecution.Tests.fs index 6f240d7c3d..5032e72d4c 100644 --- a/backend/tests/Tests/LibExecution.Tests.fs +++ b/backend/tests/Tests/LibExecution.Tests.fs @@ -24,6 +24,7 @@ module PackageRefs = LibExecution.PackageRefs module Dval = LibExecution.Dval module NR = LibParser.NameResolver module RTNR = LibExecution.RuntimeTypes.NameResolution +module RTE = LibExecution.RuntimeTypes.RuntimeError module Toplevels = LibCloud.Toplevels module Serialize = LibCloud.Serialize @@ -139,6 +140,43 @@ let runtimeErrorMessage [ "reverseTypeCheckPath", reverseTypeCheckPath; "allegedRTE", allegedRTE ] } +module RuntimeErrorFormatting = + let declaredFnAndTypeNamesArePreserved = + testTask "function and type diagnostics preserve declared names" { + let! state = executionStateFor LibDB.PackageManager.pt false Map.empty + let fnName = RT.FQFnName.fqPackage "diagnostic-rendered-fn-hash" + let fnReferenceName = [ "Display"; "fn" ] + let typeName = RT.FQTypeName.fqPackage "diagnostic-rendered-type-hash" + let declaredType = + RT.TCustomType( + { originalName = [ "Declared"; "Error" ]; resolved = Ok typeName }, + [] + ) + let actualValue = RT.DString "wrong" + let actualType = RT.Dval.toValueType actualValue + let rte = + RTE.Error.Apply( + RTE.Applications.FnParameterNotExpectedType( + fnName, + fnReferenceName, + 0, + "value", + declaredType, + actualType, + actualValue + ) + ) + let! msg = Exe.rteToString RT2DT.RuntimeError.toDT state rte |> Ply.toTask + Expect.stringContains msg "Display.fn" "uses declared function name" + Expect.stringContains msg "Declared.Error" "uses declared type name" + Expect.isFalse + (msg.Contains "diagnostic-rendered") + "does not fall back to package hashes when declared names exist" + } + + let tests = + testList "RuntimeErrorFormatting" [ declaredFnAndTypeNamesArePreserved ] + let t (_canvasName : string) @@ -372,4 +410,5 @@ let fileTests () : Test = |> Array.toList |> testList "All" -let tests = lazy (testList "LibExecution" [ fileTests () ]) +let tests = + lazy (testList "LibExecution" [ RuntimeErrorFormatting.tests; fileTests () ]) diff --git a/backend/tests/Tests/PT2RT.Tests.fs b/backend/tests/Tests/PT2RT.Tests.fs index bbf87fbf8d..9e90f84a94 100644 --- a/backend/tests/Tests/PT2RT.Tests.fs +++ b/backend/tests/Tests/PT2RT.Tests.fs @@ -365,6 +365,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Mod" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -377,6 +378,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "equals" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -517,6 +519,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "equals" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -546,6 +549,7 @@ module Expr = 1, RT.FQTypeName.fqPackage PM.Types.Enums.resultId, [], + [], "Ok", [ 2 ] ) @@ -554,6 +558,7 @@ module Expr = 3, RT.FQTypeName.fqPackage PM.Types.Enums.resultId, [], + [], "Error", [ 4 ] ) @@ -626,6 +631,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -646,6 +652,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -674,6 +681,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -706,6 +714,7 @@ module Expr = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -731,6 +740,7 @@ module Expr = (RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "multiply" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -747,6 +757,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -760,6 +771,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -783,6 +795,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Enums.withoutFields, [], + [], "Blue", [] ) ], @@ -798,6 +811,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Enums.withFields, [], + [], "Some", [ 1 ] ) ], @@ -817,6 +831,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) ], 0) @@ -833,6 +848,7 @@ module Expr = 1, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 2) ] ) @@ -841,6 +857,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.nested, [], + [], [ ("outer", 1) ] ) ], 0) @@ -859,6 +876,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) RT.GetRecordField(2, 0, "key") ], @@ -880,6 +898,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) RT.GetRecordField(2, 0, "missing") ], @@ -895,6 +914,7 @@ module Expr = 1, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 2) ] ) @@ -902,6 +922,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.nested, [], + [], [ ("outer", 1) ] ) RT.GetRecordField(3, 0, "outer") @@ -925,6 +946,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) RT.LoadVal(2, RT.DBool false) @@ -949,6 +971,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) RT.LoadVal(2, RT.DBool false) @@ -964,6 +987,7 @@ module Expr = 0, RT.FQTypeName.fqPackage PM.Types.Records.singleField, [], + [], [ ("key", 1) ] ) RT.LoadVal(2, RT.DInt64 1L) @@ -1002,6 +1026,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1031,6 +1056,36 @@ module Expr = let tests = testList "Values" [ Package.tests ] + module FnName = + let packageOriginalNameBecomesReferenceName = + let location : PT.PackageLocation = + { owner = "Canonical"; modules = [ "Module" ]; name = "chosen" } + let nameResolution : PT.NameResolution = + { originalName = [ "Alias"; "fn" ] + resolved = + Ok + { name = PT.FQFnName.fqPackage "shared-function-hash" + location = Some location } } + t + "package function reference name preserves original name without location" + (PT.EFnName(gid (), nameResolution)) + (1, + [ RT.LoadVal( + 0, + RT.DApplicable( + RT.AppNamedFn + { name = RT.FQFnName.fqPackage "shared-function-hash" + referenceName = [ "Alias"; "fn" ] + typeSymbolTable = Map.empty + typeArgs = [] + argsSoFar = [] } + ) + ) ], + 0) + + let tests = testList "FnName" [ packageOriginalNameBecomesReferenceName ] + + module Lambda = module Identity = let unapplied = @@ -1089,6 +1144,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1119,6 +1175,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1152,6 +1209,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1190,6 +1248,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1225,6 +1284,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1271,6 +1331,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1282,6 +1343,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1317,6 +1379,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1328,6 +1391,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1366,6 +1430,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1384,6 +1449,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1404,6 +1470,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1424,6 +1491,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "int64Add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1451,6 +1519,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.MyAdd.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1469,6 +1538,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.MyAdd.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1489,6 +1559,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.MyAdd.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1523,6 +1594,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "add" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1538,6 +1610,7 @@ module Expr = { name = RT.FQFnName.fqPackage E.Fns.Package.MyFnThatTakesALambda.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1561,6 +1634,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqPackage E.Fns.Package.Outer.hash + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1599,6 +1673,7 @@ module Expr = RT.DApplicable( RT.AppNamedFn { name = RT.FQFnName.fqBuiltin "printLine" 0 + referenceName = [] typeSymbolTable = Map.empty typeArgs = [] argsSoFar = [] } @@ -1631,6 +1706,7 @@ module Expr = Infix.tests Lambda.tests Fns.tests + FnName.tests Statement.tests ] diff --git a/backend/tests/Tests/Serialization.TestValues.fs b/backend/tests/Tests/Serialization.TestValues.fs index ff311d36e5..809f34159f 100644 --- a/backend/tests/Tests/Serialization.TestValues.fs +++ b/backend/tests/Tests/Serialization.TestValues.fs @@ -112,7 +112,17 @@ module RuntimeTypes = let dvals () : List = // TODO: is this exhaustive? I haven't checked. - sampleDvals () |> List.map (fun (_, (dv, _)) -> dv) + let samples = sampleDvals () |> List.map (fun (_, (dv, _)) -> dv) + let applicable = + RT.DApplicable( + RT.AppNamedFn + { name = RT.FQFnName.fqPackage "serialized-callable" + referenceName = [ "Tests"; "Serialization"; "callable" ] + typeSymbolTable = Map.empty + typeArgs = [ RT.TString ] + argsSoFar = [ RT.DString "partial" ] } + ) + samples @ [ applicable ] let dval () : RT.Dval = let typeName = RT.FQTypeName.Package hashRT diff --git a/packages/darklang/languageTools/runtimeErrors.dark b/packages/darklang/languageTools/runtimeErrors.dark index 6ca6c294d2..3e528a8ad2 100644 --- a/packages/darklang/languageTools/runtimeErrors.dark +++ b/packages/darklang/languageTools/runtimeErrors.dark @@ -58,32 +58,36 @@ module Enums = type Error = | ConstructionWrongNumberOfFields of typeName: FQTypeName.FQTypeName * + typeReferenceName: List * caseName: String * expectedFieldCount: Int * actualFieldCount: Int | ConstructionCaseNotFound of typeName: FQTypeName.FQTypeName * + typeReferenceName: List * caseName: String | ConstructionFieldOfWrongType of caseName: String * fieldIndex: Int * - expectedType: ValueType * + expectedTypeRef: TypeReference * actualType: ValueType * actualValue: Dval module Records = type Error = // -- Creation -- - | CreationTypeNotRecord of name: FQTypeName.FQTypeName + | CreationTypeNotRecord of + name: FQTypeName.FQTypeName * + typeReferenceName: List | CreationEmptyKey | CreationMissingField of fieldName: String | CreationDuplicateField of fieldName: String | CreationFieldNotExpected of fieldName: String | CreationFieldOfWrongType of fieldName: String * - expectedType: ValueType * + expectedTypeRef: TypeReference * actualType: ValueType * actualValue: Dval @@ -94,7 +98,7 @@ module Records = | UpdateFieldNotExpected of fieldName: String | UpdateFieldOfWrongType of fieldName: String * - expectedType: ValueType * + expectedTypeRef: TypeReference * actualType: ValueType * actualValue : Dval @@ -107,11 +111,11 @@ module Applications = type Error = | ExpectedApplicableButNot of actualType: ValueType * actualValue: Dval - | WrongNumberOfTypeArgsForFn of fn: FQFnName.FQFnName * expected: Int * actual: Int + | WrongNumberOfTypeArgsForFn of fn: FQFnName.FQFnName * fnReferenceName : List * expected: Int * actual: Int | CannotApplyTypeArgsMoreThanOnce - | TooManyArgsForFn of fn: FQFnName.FQFnName * expected: Int * actual: Int - | FnParameterNotExpectedType of fnName : FQFnName.FQFnName * paramIndex: Int * paramName : String * expectedType : ValueType * actualType : ValueType * actualValue : Dval - | FnResultNotExpectedType of fnName : FQFnName.FQFnName * expectedType : ValueType * actualType : ValueType * actualValue : Dval + | TooManyArgsForFn of fn: FQFnName.FQFnName * fnReferenceName : List * expected: Int * actual: Int + | FnParameterNotExpectedType of fnName : FQFnName.FQFnName * fnReferenceName : List * paramIndex: Int * paramName : String * expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval + | FnResultNotExpectedType of fnName : FQFnName.FQFnName * fnReferenceName : List * expectedTypeRef : TypeReference * actualType : ValueType * actualValue : Dval | CannotApplyTypeArgsToLambda | TooManyArgsForLambda of lambdaExprId: ID * expected: Int * actual: Int @@ -158,7 +162,7 @@ type Error = | ParseTimeNameResolution of originalName: List * error: NameResolutionError | TypeNotFound of name: FQTypeName.FQTypeName - | FnNotFound of name: FQFnName.FQFnName + | FnNotFound of name: FQFnName.FQFnName * referenceName: List | ValNotFound of name: FQValueName.FQValueName /// Invoked a package fn whose content hash is marked `Harmful` via a diff --git a/packages/darklang/languageTools/runtimeTypes.dark b/packages/darklang/languageTools/runtimeTypes.dark index 255d910099..1c8fc6034c 100644 --- a/packages/darklang/languageTools/runtimeTypes.dark +++ b/packages/darklang/languageTools/runtimeTypes.dark @@ -153,6 +153,8 @@ type LambdaImpl = type ApplicableNamedFn = { name: FQFnName.FQFnName + /// Diagnostic-only name segments from the reference site; not part of identity. + referenceName: List typeArgs: List argsSoFar: List } diff --git a/packages/darklang/prettyPrinter/runtimeError.dark b/packages/darklang/prettyPrinter/runtimeError.dark index a83d822628..fcdfe41026 100644 --- a/packages/darklang/prettyPrinter/runtimeError.dark +++ b/packages/darklang/prettyPrinter/runtimeError.dark @@ -25,7 +25,7 @@ module RuntimeError = | Count of Int * singular: ErrorSegment * plural: ErrorSegment // 2 errors, 1 error, etc // -- Functions - | FunctionName of FQFnName + | FunctionName of FQFnName * List /// Description from StdLib description fields. /// Note: may include markers like ``, to be parsed and displayed differently. | Description of String @@ -33,7 +33,7 @@ module RuntimeError = | InlineParamName of String // -- Types - | TypeName of FQTypeName + | TypeName of FQTypeName * List | ShortTypeName of FQTypeName | TypeReference of TypeReference | TypeOfValue of Dval // CLEANUP should these all just be ValueTypes? @@ -61,6 +61,8 @@ module RuntimeError = type ErrorSegments = List + let emptyReferenceName : List = [] + let segmentsToString (branchId: Uuid) (segments: ErrorSegments) : String = let reversed = Stdlib.List.reverse segments @@ -89,12 +91,15 @@ module RuntimeError = | None -> "" | Some prev -> Stdlib.String.articleFor prev ++ " " - | FunctionName fn -> fnName branchId fn + | FunctionName(fn, referenceName) -> + referencedFnName branchId fn referenceName + | Description d -> d | ParamName p -> $"`{p}`" | InlineParamName p -> p // Inline versions don't have quotes - | TypeName t -> typeName branchId t + | TypeName(t, referenceName) -> + referencedTypeName branchId t referenceName | ValueName v -> valueName branchId v | ShortTypeName t -> // TODO: make it short @@ -264,15 +269,15 @@ module RuntimeError = | TypeNotFound name -> [ ES.String "Type " - ES.TypeName name + ES.TypeName(name, emptyReferenceName) ES.String " couldn't be found" ] | ValNotFound name -> [ ES.String "Value " ES.ValueName name ES.String " couldn't be found" ] - | FnNotFound name -> + | FnNotFound(name, referenceName) -> [ ES.String "Function " - ES.FunctionName name + ES.FunctionName(name, referenceName) ES.String " couldn't be found" ] | DeprecatedItemHalted _target -> @@ -282,9 +287,9 @@ module RuntimeError = | Record err -> match err with // -- Creation -- - | CreationTypeNotRecord name -> + | CreationTypeNotRecord(name, typeReferenceName) -> [ ES.String "Expected a record, but " - ES.TypeName name + ES.TypeName(name, typeReferenceName) ES.String " is not one" ] | CreationEmptyKey -> [ ES.String "Empty key in record creation" ] | CreationMissingField fieldName -> @@ -306,10 +311,11 @@ module RuntimeError = // ES.TypeName typeName // ES.String " record" ] - | CreationFieldOfWrongType (fieldName, expectedType, actualType, actualValue) -> + | CreationFieldOfWrongType (fieldName, expectedTypeRef, actualType, actualValue) -> [ ES.String "Failed to create record. " ES.String "Expected " - ES.ValueType expectedType + // Custom type refs keep their diagnostic name. + ES.TypeReference expectedTypeRef ES.String " for field " ES.FieldName fieldName ES.String ", but got " @@ -337,10 +343,10 @@ module RuntimeError = // ES.TypeName typeName // ES.String " record" ] - | UpdateFieldOfWrongType(fieldName, expectedType, actualType, actualValue) -> + | UpdateFieldOfWrongType(fieldName, expectedTypeRef, actualType, actualValue) -> [ ES.String "Failed to create updated record. " ES.String "Expected " - ES.ValueType expectedType + ES.TypeReference expectedTypeRef ES.String " for field " ES.FieldName fieldName ES.String ", but got " @@ -369,26 +375,26 @@ module RuntimeError = | Enum err -> match err with - | ConstructionWrongNumberOfFields (typeName, caseName, expectedFieldCount, actualFieldCount) -> + | ConstructionWrongNumberOfFields (typeName, typeReferenceName, caseName, expectedFieldCount, actualFieldCount) -> [ ES.String "Expected " ES.Int expectedFieldCount ES.String " fields in " - ES.TypeName typeName + ES.TypeName(typeName, typeReferenceName) ES.String "." ES.FieldName caseName ES.String ", but got " ES.Int actualFieldCount ] - | ConstructionCaseNotFound (typeName, caseName) -> + | ConstructionCaseNotFound (typeName, typeReferenceName, caseName) -> [ ES.String "There is no case named " ES.FieldName caseName ES.String " in " - ES.TypeName typeName ] + ES.TypeName(typeName, typeReferenceName) ] - | ConstructionFieldOfWrongType (caseName, fieldIndex, expectedType, actualType, actualValue) -> + | ConstructionFieldOfWrongType (caseName, fieldIndex, expectedTypeRef, actualType, actualValue) -> [ ES.String "Failed to create enum. " ES.String "Expected " - ES.ValueType expectedType + ES.TypeReference expectedTypeRef ES.String " for field " ES.Int fieldIndex ES.String " in " @@ -418,37 +424,38 @@ module RuntimeError = ES.FullValue actualValue ES.String ")" ] - | WrongNumberOfTypeArgsForFn (fn, expected, actual) -> - [ ES.FunctionName fn + | WrongNumberOfTypeArgsForFn (fn, fnReferenceName, expected, actual) -> + [ ES.FunctionName(fn, fnReferenceName) ES.String " expects " ES.Count(expected, ES.String "type argument", ES.String "type arguments") ES.String ", but got " ES.Count(actual, ES.String "type argument", ES.String "type arguments") ] | CannotApplyTypeArgsMoreThanOnce -> [ ES.String "Cannot apply type arguments more than once" ] - | TooManyArgsForFn (fn, expected, actual) -> - [ ES.FunctionName fn + | TooManyArgsForFn (fn, fnReferenceName, expected, actual) -> + [ ES.FunctionName(fn, fnReferenceName) ES.String " expects " ES.Count(expected, ES.String "argument", ES.String "arguments") ES.String ", but got " ES.Count(actual, ES.String "argument", ES.String "arguments") ] - | FnParameterNotExpectedType(fnName, paramIndex, paramName, expectedType, actualType, actualValue) -> - [ ES.FunctionName fnName + | FnParameterNotExpectedType(fnName, fnReferenceName, paramIndex, paramName, expectedTypeRef, actualType, actualValue) -> + [ ES.FunctionName(fnName, fnReferenceName) ES.String "'s " ES.Ordinal(paramIndex + 1) ES.String " parameter " ES.ParamName paramName ES.String " expects " - ES.ValueType expectedType + // Custom type refs keep their diagnostic name. + ES.TypeReference expectedTypeRef ES.String ", but got " ES.ValueType actualType ES.String " (" ES.FullValue actualValue ES.String ")" ] - | FnResultNotExpectedType(fnName, expectedType, actualType, actualValue) -> - [ ES.FunctionName fnName + | FnResultNotExpectedType(fnName, fnReferenceName, expectedTypeRef, actualType, actualValue) -> + [ ES.FunctionName(fnName, fnReferenceName) ES.String "'s return value expects " - ES.ValueType expectedType + ES.TypeReference expectedTypeRef ES.String ", but got " ES.ValueType actualType ES.String " (" @@ -483,9 +490,9 @@ module RuntimeError = [ ES.String "Unsupported type in JSON: " ES.TypeReference typ ES.String ". Some types are not supported in Json serialization, and cannot be used as arguments to " - ES.FunctionName (LanguageTools.RuntimeTypes.FQFnName.FQFnName.Builtin(LanguageTools.RuntimeTypes.FQFnName.Builtin { name = "jsonParse"; version = 0l })) + ES.FunctionName (LanguageTools.RuntimeTypes.FQFnName.FQFnName.Builtin(LanguageTools.RuntimeTypes.FQFnName.Builtin { name = "jsonParse"; version = 0l }), emptyReferenceName) ES.String " or " - ES.FunctionName (LanguageTools.RuntimeTypes.FQFnName.FQFnName.Builtin(LanguageTools.RuntimeTypes.FQFnName.Builtin { name = "jsonSerialize"; version = 0l })) ] + ES.FunctionName (LanguageTools.RuntimeTypes.FQFnName.FQFnName.Builtin(LanguageTools.RuntimeTypes.FQFnName.Builtin { name = "jsonSerialize"; version = 0l }), emptyReferenceName) ] | CannotSerializeValue dv -> [ ES.String "Cannot serialize " ES.FullValue dv diff --git a/packages/darklang/prettyPrinter/runtimeTypes.dark b/packages/darklang/prettyPrinter/runtimeTypes.dark index 6cfe571be6..d8dda1cdd7 100644 --- a/packages/darklang/prettyPrinter/runtimeTypes.dark +++ b/packages/darklang/prettyPrinter/runtimeTypes.dark @@ -32,7 +32,7 @@ let packageName | "Tests" -> $"{modulesPart}{name}" | _ -> $"{owner}.{modulesPart}{name}" -/// Given PM locations for a hash, resolve to best display name. +/// Given PM locations for a hash, resolve to best reference name. /// Uses pickLocation with context to choose among multiple locations. let resolvePackageHash (ctx: LanguageTools.PackageManager.PickContext) @@ -43,7 +43,8 @@ let resolvePackageHash | Some l -> packageName l.owner l.modules l.name | None -> Darklang.LanguageTools.RuntimeTypes.hashToString hash -/// Like resolvePackageHash but prefers originalName when ambiguous (multiple locations). +/// Render a package reference for diagnostics. When a hash has multiple +/// matching package locations, use the name from the reference site. let resolvePackageHashWithOriginalName (ctx: LanguageTools.PackageManager.PickContext) (locations: List) @@ -54,8 +55,15 @@ let resolvePackageHashWithOriginalName | [ single ] -> packageName single.owner single.modules single.name | _ -> match originalName with - | _ :: _ -> Stdlib.String.join originalName "." | [] -> resolvePackageHash ctx locations hash + | segments -> + let matched = + locations + |> Stdlib.List.findFirst (fun l -> + (Stdlib.List.append (Stdlib.List.append [ l.owner ] l.modules) [ l.name ]) == segments) + match matched with + | Some l -> packageName l.owner l.modules l.name + | None -> Stdlib.String.join segments "." module FQTypeName = @@ -73,6 +81,20 @@ let typeName match t with | Package p -> FQTypeName.package branchId p +/// Like `typeName`, but uses the name from the reference site to break hash ties. +let referencedTypeName + (branchId: Uuid) + (t: LanguageTools.RuntimeTypes.FQTypeName.FQTypeName) + (referenceName: List) + : String = + match t with + | Package p -> + resolvePackageHashWithOriginalName + LanguageTools.PackageManager.PickContext.empty + (Builtin.pmGetLocationsByType branchId p) + referenceName + p + module FQValueName = let builtIn (t: LanguageTools.RuntimeTypes.FQValueName.Builtin) : String = @@ -130,6 +152,21 @@ let fnName | Builtin b -> FQFnName.builtIn b | Package p -> FQFnName.package branchId p +/// Like `fnName`, but uses the name from the call site to break hash ties. +let referencedFnName + (branchId: Uuid) + (t: LanguageTools.RuntimeTypes.FQFnName.FQFnName) + (referenceName: List) + : String = + match t with + | Builtin b -> FQFnName.builtIn b + | Package p -> + resolvePackageHashWithOriginalName + LanguageTools.PackageManager.PickContext.empty + (Builtin.pmGetLocationsByFn branchId p) + referenceName + p + let typeReference @@ -209,7 +246,9 @@ let typeReference | TDB inner -> $"DB<{typeReference branchId inner}>" - | TVariable varName -> "'" ++ varName + | TVariable varName -> + // "_" marks a runtime-unknown type (ValueType.Unknown), not a named variable. + if varName == "_" then "_" else "'" ++ varName let knownType @@ -526,7 +565,10 @@ module Dval = | DApplicable(AppNamedFn namedFn) -> - PrettyPrinter.RuntimeTypes.fnName branchId namedFn.name + PrettyPrinter.RuntimeTypes.referencedFnName + branchId + namedFn.name + namedFn.referenceName | DApplicable(AppLambda _lambda) -> // // Note: this use case is safe (RE docs/dblock-serialization.md)