Skip to content

fix(hir): #832 — class decorator identity-return (return target) accepted as no-op#840

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-832-decorator-identity-return
May 16, 2026
Merged

fix(hir): #832 — class decorator identity-return (return target) accepted as no-op#840
proggeramlug merged 1 commit into
mainfrom
worktree-fix-832-decorator-identity-return

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

Root cause

append_decorator_invocations_inner in crates/perry-hir/src/lower.rs emitted if (__ret !== undefined) throw TypeError(...) — strict on any non-undefined return. Real-world decorators (TypeORM @Entity, NestJS, GraphQL wrappers, decorator-factory output not simplified for production) routinely end with return target / return descriptor as a no-op, and the trivial identity decorator from the bug report was rejected too.

Fix

Augment the condition to also accept identity returns:

if (__perry_dec_ret !== undefined && __perry_dec_ret !== target) {
  throw TypeError(...);
}

target comes from invocation_args.first() — the Expr::ClassRef(class.name) that append_class_decorator_invocations passes as the decorator's first argument. So the runtime comparison is between the decorator's return value and the original class reference it received. Falls back to the strict-undefined check if invocation_args is empty (defensive).

Test plan

Closes #832. Refs #793, #801, #754.

…cepted as no-op

`append_decorator_invocations_inner` (PR #754) threw on any non-undefined
decorator return value to surface the silent class-replacement-not-installed
case. That gate was too strict — many real-world decorators (TypeORM, NestJS,
GraphQL wrappers, any factory output that hasn't been simplified for
production) end with `return target` as a benign no-op, and the trivial
identity decorator from the bug report was rejected as well.

Augment the condition to accept identity returns:
`if (__ret !== undefined && __ret !== target) throw ...`. The target reference
comes from `invocation_args.first()` — the `Expr::ClassRef` that
`append_class_decorator_invocations` passes as the decorator's first
argument. Genuine class replacement (`return SomeOtherClass`) still throws
with the same actionable message, preserving the #754 safety guarantee.

Closes #832.
@proggeramlug proggeramlug force-pushed the worktree-fix-832-decorator-identity-return branch from 1b224af to 3362228 Compare May 16, 2026 09:57
@proggeramlug proggeramlug merged commit f74c18e into main May 16, 2026
9 checks passed
@proggeramlug proggeramlug deleted the worktree-fix-832-decorator-identity-return branch May 16, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

decorator that returns the target class is rejected; Node accepts it

1 participant