Author: David Munoz Tord
Date: 2026-04-15

# hotpatchR Overview

#### David Munoz Tord

#### 2026-04-15

# Introduction

This vignette explains the `hotpatchR` design and why runtime namespace patching is the right tool for legacy hotfix workflows.

## The legacy package lockdown problem

A loaded R package lives in a locked namespace. Internal functions are resolved from inside the package bubble, so calling a hidden helper from the global environment does not change the package’s internal behavior.

This is the namespace trap that makes legacy hotfixing difficult: a visible exported function may still invoke a broken internal helper even after you have sourced a fixed version elsewhere.

## Why the global workaround fails

The usual workaround is:

- identify the broken internal function
- find every package caller that depends on it
- copy the broken internals into a hotfix script
- source the script into the global environment
- run tests and hope the package resolves the new bindings

That workflow is brittle because a bug in one hidden helper can force you to patch many callers, even when only one implementation needs to change.

## hotpatchR philosophy

Instead of pulling functions out into the global environment, `hotpatchR` performs surgical edits inside the package namespace. That means:

- fix only the broken internal function
- exported callers automatically resolve to the updated implementation
- the package remains loaded and otherwise unchanged

## Basic workflow

Before using the package, install it from CRAN or GitHub:

    install.packages("hotpatchR")
    # or, for the development version:
    # remotes::install_github("munoztd0/hotpatchR")

The package includes a real example of this pattern with an exported parent function and an internal child helper.

    library(hotpatchR)

    baseline <- dummy_parent_func("test")
    print(baseline)
    #> [1] "Parent output -> I am the BROKEN child. Input: test"
    #> "Parent output -> I am the BROKEN child. Input: test"

    inject_patch(
      pkg = "hotpatchR",
      patch_list = list(
        dummy_child_func = function(x) {
          paste("I am the FIXED child! Input:", x)
        }
      )
    )

    patched_result <- dummy_parent_func("test")
    print(patched_result)
    #> [1] "Parent output -> I am the FIXED child! Input: test"
    #> "Parent output -> I am the FIXED child! Input: test"

## How inject_patch works

`inject_patch()`

1. identifies the target namespace or environment
2. unlocks the binding for the named object
3. assigns the replacement function into that environment
4. re-locks the binding

Because the replacement function can be defined with the package namespace as its parent, it still has access to the package’s internal helpers.

## Rolling back a patch

If you need to restore the original binding, `undo_patch()` reverses the previous change.

    undo_patch(pkg = "hotpatchR", names = "dummy_child_func")
    restored_result <- dummy_parent_func("test")
    print(restored_result)
    #> [1] "Parent output -> I am the BROKEN child. Input: test"
    #> "Parent output -> I am the BROKEN child. Input: test"

## Hotfix scripts

`apply_hotfix_file()` is a convenience wrapper for scripted hotfix application. A compatible hotfix file should define:

- `pkg` (optional, if not passed explicitly)
- `patch_list`, a named list of replacement functions

Example hotfix file for this package:

    #
    pkg <- "hotpatchR"
    patch_list <- list(
      dummy_child_func = function(x) {
        paste("I am the FIXED child! Input:", x)
      }
    )

Then apply it with:

    apply_hotfix_file("dev/hotpatchR_hotfix.R")

## Next steps

The current package is focused on the core runtime patching path. Future enhancements may include patch comparison, dependency scanning, and CI-friendly test wrappers that explicitly preserve the patched namespace during test execution.