ChezScheme icon indicating copy to clipboard operation
ChezScheme copied to clipboard

Combination of re-exports with identifier properties and renaming imports causes spurious ‘multiple definitions’ errors

Open dpk opened this issue 8 months ago • 6 comments

(library (beep-lib)
  (export cons beep)
  (import (chezscheme))

  (define beep 'beep)
  (define-property cons beep #t))

(library (boop-lib)
  (export button)
  (import (chezscheme)
          (rename (beep-lib)
                  (beep boop)))

  (define (button) (display boop)))

Attempting to load and import the (boop-lib) causes an error:

Exception: multiple definitions for cons in body (library (boop-lib) (export button) (import (chezscheme) (rename (...) (...))) (define (button) (display boop))) at line 1, char 1 of boop-lib.sls

This is incorrect because cons has the same binding in both libraries from which (boop-lib) imports it, and there is no identifier property conflict.

This only happens when one of the bindings imported from the re-exporting library is renamed. This library works fine:

(library (button-lib)
  (export button)
  (import (chezscheme)
          (beep-lib))

  (define (button) (display beep)))

(Chez Scheme Version 10.1.0)

dpk avatar Mar 28 '25 09:03 dpk

Chez Scheme does not fully specify the behaviour of identifier properties. In particular, it is not clear whether cons exported from beep-lib and cons exported from (chezscheme) should be considered to have the same binding or not. If they are considered to have the same binding, your example should work. Otherwise, it shouldn't. You see the strange behaviour because the implementation takes some freedom allowed by the unspecifiedness in different code paths. For example, the following goes through without an error:

(library (beep-lib) (export cons) (import (scheme))
  (define-property cons cons cons))
(library (boop-lib) (export cons) (import (scheme))
  (define-property cons cons car))
(top-level-program (import (scheme) (beep-lib) (boop-lib))
  (car (cons 1 2)))

If all versions of cons are assumed to have the same binding, it is unclear what the formal semantic model should be. If different property lists can be attached to the same binding, they cannot be part of that binding but have to be recorded in an extra table, which has to live somewhere.

As a side note, Chez's free-identifier=? ignores properties.

(let ((x 1))
    (alias y x)
    (define-property y + #t)
    (free-identifier=? #'x #'y))   ; => #t

Racket's binding spaces provide a similar mechanism but with a more complete specification (which is easier for Racket because it is not based on an existing standard). Conceptually, in the syntax model of RnRS, Scheme is no longer a Lisp-1 but a Lisp-∞. Each identifier does not necessarily have a single binding (if any) but possibly multiple ones: the standard binding and further bindings keyed by other bindings (in Racket, these are the (binding) spaces).

As a final note, it is tempting to use identifier properties to attach extra meaning to existing identifiers when implementing DSLs. For such use cases, you want the code examples you gave to work. However, that idea is not a particularly good one; it should not matter whether an identifier like let that you want to reuse in your DSL for, say, an ML-like language, has a binding in the standard library or not (that you would have to extend). The right way is to use the machinery given by Chez Scheme's (local) modules and local imports.

On the other hand, identifier properties work fine if you control the default binding.

mnieper avatar Apr 23 '25 07:04 mnieper

It seems definitely a bug to me that how one identifier is imported can cause an otherwise distinct identifier to fail to import.

dpk avatar Apr 23 '25 11:04 dpk

It seems definitely a bug to me that how one identifier is imported can cause an otherwise distinct identifier to fail to import.

Technically, the cause is not the otherwise distinct identifier but the form of the import (unmodified library interface vs. customised).

I agree that the current behaviour is confusing to the user and hides errors; Chez Scheme should emit an error in all cases. I can create a pull request. At the moment, Chez is also silent in case of program code like the following:

(library (foo)
  (export cons)
  (import (scheme))
  (define-property cons + 1))
(library (bar)
  (export cons)
  (import (scheme))
  (define-property cons + 2))
(top-level-program
  (import (scheme) (bar) (foo))
  (define-syntax get-property
    (lambda (x)
      (lambda (r)
        (syntax-case x ()
          [(_ id key)
         #`'#,(datum->syntax #'* (r #'id #'key))]))))
  (pretty-print (get-property cons +)))

The printed number will depend on the order in which (bar) and (foo) are imported.

mnieper avatar Apr 23 '25 11:04 mnieper

Your proposed solution seems to go against (the PFN to) your SRFI 213.

dpk avatar Apr 23 '25 11:04 dpk

Your proposed solution seems to go against (the PFN to) your SRFI 213.

One reason why the PFN wasn't in the final version of SRFI 213 is that Chez Scheme doesn't support it. The PFN is not fully satisfactory anyway, because there is no way to selectively import or export properties (which is present in Racket and Rhombus).

mnieper avatar Apr 23 '25 13:04 mnieper

We could potentially fix that in R7RS Large if you think it’s a big issue. I think it suffices to restrict the public interface for accessing a particular property.

dpk avatar Apr 23 '25 14:04 dpk