From cc9c8c02133fbcf2d55f10630f934b697275ae7e Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Wed, 18 Dec 2024 21:32:20 +0100 Subject: [PATCH] Carry both call-site and definition site in Effect.fn, auto-trace to anon (#4155) Co-authored-by: Tim --- .changeset/quiet-kings-impress.md | 5 + .changeset/weak-pears-remember.md | 5 + packages/effect/src/Effect.ts | 148 ++++++++++++++++++++------ packages/effect/src/internal/cause.ts | 14 ++- packages/effect/src/internal/core.ts | 18 +++- 5 files changed, 147 insertions(+), 43 deletions(-) create mode 100644 .changeset/quiet-kings-impress.md create mode 100644 .changeset/weak-pears-remember.md diff --git a/.changeset/quiet-kings-impress.md b/.changeset/quiet-kings-impress.md new file mode 100644 index 00000000000..828632292a3 --- /dev/null +++ b/.changeset/quiet-kings-impress.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +add Effect.fnUntraced - an untraced version of Effect.fn diff --git a/.changeset/weak-pears-remember.md b/.changeset/weak-pears-remember.md new file mode 100644 index 00000000000..e28a4dd11f2 --- /dev/null +++ b/.changeset/weak-pears-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Carry both call-site and definition site in Effect.fn, auto-trace to anon diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index cee4d82cde2..75d93facf79 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -37,6 +37,7 @@ import * as layer from "./internal/layer.js" import * as query from "./internal/query.js" import * as _runtime from "./internal/runtime.js" import * as _schedule from "./internal/schedule.js" +import * as internalTracer from "./internal/tracer.js" import type * as Layer from "./Layer.js" import type { LogLevel } from "./LogLevel.js" import type * as ManagedRuntime from "./ManagedRuntime.js" @@ -60,7 +61,7 @@ import type * as Supervisor from "./Supervisor.js" import type * as Tracer from "./Tracer.js" import type { Concurrency, Contravariant, Covariant, NoExcessProperties, NoInfer, NotFunction } from "./Types.js" import type * as Unify from "./Unify.js" -import { internalCall, isGeneratorFunction, type YieldWrap } from "./Utils.js" +import { isGeneratorFunction, type YieldWrap } from "./Utils.js" /** * @since 2.0.0 @@ -11576,9 +11577,28 @@ export const fn: name: string, options?: Tracer.SpanOptions ) => fn.Gen & fn.NonGen) = function(nameOrBody: Function | string, ...pipeables: Array) { + const limit = Error.stackTraceLimit + Error.stackTraceLimit = 2 + const errorDef = new Error() + Error.stackTraceLimit = limit if (typeof nameOrBody !== "string") { - return function(this: any) { - return fnApply(this, nameOrBody, arguments as any, pipeables) + return function(this: any, ...args: Array) { + const limit = Error.stackTraceLimit + Error.stackTraceLimit = 2 + const errorCall = new Error() + Error.stackTraceLimit = limit + return fnApply({ + self: this, + body: nameOrBody, + args, + pipeables, + spanName: "", + spanOptions: { + context: internalTracer.DisablePropagation.context(true) + }, + errorDef, + errorCall + }) } as any } const name = nameOrBody @@ -11587,53 +11607,111 @@ export const fn: return function(this: any, ...args: Array) { const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 - const error = new Error() + const errorCall = new Error() Error.stackTraceLimit = limit - let cache: false | string = false - const captureStackTrace = () => { - if (cache !== false) { - return cache - } - if (error.stack) { - const stack = error.stack.trim().split("\n") - cache = stack.slice(2).join("\n").trim() - return cache - } - } - const effect = fnApply(this, body, args, pipeables) - const opts: any = (options && "captureStackTrace" in options) ? options : { captureStackTrace, ...options } - return withSpan(effect, name, opts) + return fnApply({ + self: this, + body, + args, + pipeables, + spanName: name, + spanOptions: options, + errorDef, + errorCall + }) } } } -function fnApply(self: any, body: Function, args: Array, pipeables: Array) { +function fnApply(options: { + readonly self: any + readonly body: Function + readonly args: Array + readonly pipeables: Array + readonly spanName: string + readonly spanOptions: Tracer.SpanOptions + readonly errorDef: Error + readonly errorCall: Error +}) { let effect: Effect let fnError: any = undefined - if (isGeneratorFunction(body)) { - effect = gen(() => internalCall(() => body.apply(self, args))) + if (isGeneratorFunction(options.body)) { + effect = core.fromIterator(() => options.body.apply(options.self, options.args)) } else { try { - effect = body.apply(self, args) + effect = options.body.apply(options.self, options.args) } catch (error) { fnError = error effect = die(error) } } - if (pipeables.length === 0) { - return effect + if (options.pipeables.length > 0) { + try { + for (const x of options.pipeables) { + effect = x(effect) + } + } catch (error) { + effect = fnError + ? failCause(internalCause.sequential( + internalCause.die(fnError), + internalCause.die(error) + )) + : die(error) + } } - try { - for (const x of pipeables) { - effect = x(effect) + + let cache: false | string = false + const captureStackTrace = () => { + if (cache !== false) { + return cache + } + if (options.errorCall.stack) { + const stackDef = options.errorDef.stack!.trim().split("\n") + const stackCall = options.errorCall.stack.trim().split("\n") + cache = `${stackDef.slice(2).join("\n").trim()}\n${stackCall.slice(2).join("\n").trim()}` + return cache } - } catch (error) { - effect = fnError - ? failCause(internalCause.sequential( - internalCause.die(fnError), - internalCause.die(error) - )) - : die(error) } - return effect + const opts: any = (options.spanOptions && "captureStackTrace" in options.spanOptions) + ? options.spanOptions + : { captureStackTrace, ...options.spanOptions } + return withSpan(effect, options.spanName, opts) } + +/** + * Creates a function that returns an Effect. + * + * The function can be created using a generator function that can yield + * effects. + * + * `Effect.fnUntraced` also acts as a `pipe` function, allowing you to create a pipeline after the function definition. + * + * @example + * ```ts + * // Title: Creating a traced function with a generator function + * import { Effect } from "effect" + * + * const logExample = Effect.fnUntraced(function*(n: N) { + * yield* Effect.annotateCurrentSpan("n", n) + * yield* Effect.logInfo(`got: ${n}`) + * yield* Effect.fail(new Error()) + * }) + * + * Effect.runFork(logExample(100)) + * ``` + * + * @since 3.12.0 + * @category function + */ +export const fnUntraced: fn.Gen = (body: Function, ...pipeables: Array) => + pipeables.length === 0 + ? function(this: any, ...args: Array) { + return core.fromIterator(() => body.apply(this, args)) + } + : function(this: any, ...args: Array) { + let effect = core.fromIterator(() => body.apply(this, args)) + for (const x of pipeables) { + effect = x(effect) + } + return effect + } diff --git a/packages/effect/src/internal/cause.ts b/packages/effect/src/internal/cause.ts index 38bcbb4ddd1..3677c88d1f5 100644 --- a/packages/effect/src/internal/cause.ts +++ b/packages/effect/src/internal/cause.ts @@ -1073,7 +1073,7 @@ export const prettyErrorMessage = (u: unknown): string => { return stringifyCircular(u) } -const locationRegex = /\((.*)\)/ +const locationRegex = /\((.*)\)/g /** @internal */ export const spanToTrace = globalValue("effect/Tracer/spanToTrace", () => new WeakMap()) @@ -1105,9 +1105,15 @@ const prettyErrorStack = (message: string, stack: string, span?: Span | undefine if (typeof stackFn === "function") { const stack = stackFn() if (typeof stack === "string") { - const locationMatch = stack.match(locationRegex) - const location = locationMatch ? locationMatch[1] : stack.replace(/^at /, "") - out.push(` at ${current.name} (${location})`) + const locationMatchAll = stack.matchAll(locationRegex) + let match = false + for (const [, location] of locationMatchAll) { + match = true + out.push(` at ${current.name} (${location})`) + } + if (!match) { + out.push(` at ${current.name} (${stack.replace(/^at /, "")})`) + } } else { out.push(` at ${current.name}`) } diff --git a/packages/effect/src/internal/core.ts b/packages/effect/src/internal/core.ts index 907e166e46c..1b515a58c67 100644 --- a/packages/effect/src/internal/core.ts +++ b/packages/effect/src/internal/core.ts @@ -1427,13 +1427,23 @@ export const whileLoop = ( } /* @internal */ -export const gen: typeof Effect.gen = function() { - const f = arguments.length === 1 ? arguments[0] : arguments[1].bind(arguments[0]) - return suspend(() => { +export const fromIterator = >, AEff>( + iterator: LazyArg> +): Effect.Effect< + AEff, + [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? E : never, + [Eff] extends [never] ? never : [Eff] extends [YieldWrap>] ? R : never +> => + suspend(() => { const effect = new EffectPrimitive(OpCodes.OP_ITERATOR) as any - effect.effect_instruction_i0 = f(pipe) + effect.effect_instruction_i0 = iterator() return effect }) + +/* @internal */ +export const gen: typeof Effect.gen = function() { + const f = arguments.length === 1 ? arguments[0] : arguments[1].bind(arguments[0]) + return fromIterator(() => f(pipe)) } /* @internal */