Carp/test/test-for-errors/inner-private-bindings.carp
Scott Olsen 2701517753
fix: don't expand inner module macros on first pass; privacy (#1216)
* fix: don't expand inner module macros on first pass; privacy

This commit changes the behavior of expansions to avoid expanding module
expressions until we're actually processing the module in question.

Previously, the following form would be entirely expanded at the time of evaluating A:

```clojure
(defmodule A <- current environment

  (some-macro) <- expand

  (defmodule B
    (some-macro f) <- expand, current env is A, *NOT* B.
    so if this expands to
    (private f)
    (defn f ....)
    the f of the expansion is added to *A*, and we have a duplicate
    ghost binder.
  )

  (defn foo [] B.f) <- expand, B.f does not exist yet, any meta on the
  binding will be ignored, permitting privacy errors since expansion
  ignores undefined bindings, instead, we'll look this up at eval time,
  and not check privacy as doing so would cause problems for legitimate
  cases.
)
```

This meant that if the macro happened to have side-effects, e.g. calling
`meta-set!` we'd produce side-effects in the wrong environment, A,
resulting in duplicate bindings, missing bindings at evaluation time,
and other problems.

Now, we instead process the form as follows:

```clojure
(defmodule A <- current environment

  (some-macro) <- expand

  (defmodule B
    (some-macro f) <- wait
  )

  (defn foo [] B.f)
)

;; step 2
(defmodule A

  (foo-bar ) <- previously expanded macro

  (defmodule B <- current environment
    (some-macro f) <- expand
  )
  ....
)
```

In general, this prevents the generation of a bunch of unintentional and
incorrectly added bindings when calling `meta-set!` from various macros.

Additionally, privacy constraints are now carried across nested modules:

```
(defmodule A
  (defmodule B
    (private f)
    (defn f [] 0)
  )
  (defn g [] (B.f)) ;; Privacy error!
)
```

This change also fixed an issue whereby recursive functions with `sig`
annotations could trick the compiler. Again, this had to do with the
unintentionally added bindings stemming from expansion of nested module
expressions via meta-set.

Fixes #1213, Fixes #467

* fix: ensure we check privacy against the path of found binders
2021-05-24 21:04:10 +02:00

9 lines
139 B
Plaintext

(Project.config "file-path-print-length" "short")
(defmodule A
(defmodule B
(private f)
(defn f [] 1)
)
(defn g [] (B.f))
)