I think the problem is easier to see if we replace (state-component a) with its expansion, as reported by the macro stepper:
#lang racket
(require (for-syntax (only-in syntax/transformer
make-variable-like-transformer)))
(struct state (a)
#:mutable
#:constructor-name make-state
#:omit-define-syntaxes)
(define state (make-state 1))
(define-syntax a
(make-variable-like-transformer
(state-a state)
(λ (v)
(set-state-a! state v))))
This is trying to use state, state-a, and set-state-a! at compile time, but those values don’t exist until runtime. You need to pass make-variable-like-transformer a syntax-quoted #'(state-a state) that it should expand to, not the value of the expression (state-a state).
Here is a working version, with only two other changes I couldn’t resist: generating names is much better with format-id, and using the scope from #'id rather than stx is more robust if some other macro might expand to state-component:
#lang racket
(require (for-syntax (only-in racket/syntax
format-id)
(only-in syntax/transformer
make-variable-like-transformer)))
(module+ test
(require rackunit)
(check-eqv? a 1)
(set! a 5)
(check-eqv? a 5))
(struct state (a)
#:mutable
#:constructor-name make-state
#:omit-define-syntaxes)
(define state (make-state 1))
(define-syntax (state-component stx)
(syntax-case stx ()
[(_ id)
(with-syntax
([state-id (format-id #'id "state-~a" #'id)]
[set-state-id! (format-id #'id "set-state-~a!" #'id)])
#`(define-syntax id
(make-variable-like-transformer
#'(state-id state)
#'(λ (v)
(set-state-id! state v)))))]))
(state-component a)
Looking beyond the immediate problem, I’d guess that you have written define-state-component because, in reality, you have several components. I am allergic to writing out lists of fields multiple times—in the struct declaration, in the arguments to make-state, and in the uses of define-state-component—and I’d be sad that DrRacket’s scope-aware renaming couldn’t handle a. I would be inclined to do something like this:
#lang racket
(require (for-syntax racket/syntax
syntax/transformer)
syntax/parse/define)
(module+ test
(require rackunit)
(check-eqv? a 1)
(set! a 5)
(check-eqv? a 5)
(check-equal? b '(a b c))
(set! b 'done)
(check-eq? b 'done))
(define-syntax-parse-rule (define-state name:id
(~seq field:id init-val:expr) ...)
#:with make-name (format-id #'name "make-~a" #'name #:subs? #t)
#:with ((~seq field-ref set-field!) ...)
(for*/list ([fld (in-list (attribute field))]
[fmt `("~a-~a" "set-~a-~a!")])
(format-id fld fmt #'name fld))
(begin
(struct name (field ...)
#:mutable
#:constructor-name make-name
#:omit-define-syntaxes)
(define name
(make-name init-val ...))
(define-syntax field
(make-variable-like-transformer
(quote-syntax (field-ref name))
(quote-syntax (λ (v)
(set-field! name v)))))
...))
(define-state state
a 1
b '(a b c))
In real life, I did something like this to define the web server’s “safety limits” construct with make-safety-limits and make-unlimited-safety-limits.
Often, when we talk about why macros are so useful, we give examples of using macros to implement new linguistic constructs. But I think the macro that is used exactly once is an underappreciated genre!