1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Regression tests for PR #353: a defrecord's fields must be in scope as bare
//! symbols inside an inline protocol method body, and a parameter sharing a
//! field's name must shadow the field.
//!
//! `(defrecord R [sha] P (mutable? [_] (valid-sha? sha)))` threw
//! "unbound symbol: sha": a method impl is built as an ordinary fn whose only
//! bindings are its own params, while Clojure compiles the fields as instance
//! fields of the generated class, so they are simply in scope.
//!
//! These cover both halves of `build_impl_fn`'s field plumbing — the synthesised
//! `(let* [f (:f this) ...] body)` and the cases that must NOT get one (reify,
//! extend-type, a param that already takes the field's name).
use std::sync::Arc;
use cljrs_reader::Parser;
use cljrs_runtime::env::env::{Env, GlobalEnv};
use cljrs_value::Value;
fn make_env() -> (Arc<GlobalEnv>, Env) {
let globals = cljrs_runtime::Runtime::builder()
.execution_mode(cljrs_runtime::ExecutionMode::TreeWalk)
.build()
.expect("runtime")
.into_globals();
let env = Env::new(globals.clone(), "user");
(globals, env)
}
/// Evaluate `src` and return the last value rendered with `pr-str`.
fn eval_pr(src: &str) -> String {
let (_globals, mut env) = make_env();
let mut parser = Parser::new(src.to_string(), "<test>".to_string());
let forms = parser.parse_all().expect("parse error");
let mut result = Value::Nil;
for form in forms {
result = cljrs_runtime::interp::eval::eval(&form, &mut env).expect("eval error");
}
match result {
Value::Str(s) => s.get().as_str().to_string(),
other => panic!("expected a string from pr-str, got {:?}", other),
}
}
// ── Fields are in scope as bare symbols ──────────────────────────────────────
#[test]
fn bare_field_symbols_resolve_in_a_method_body() {
assert_eq!(
eval_pr(
"(defprotocol P (describe [this]))
(defrecord R [a b] P (describe [_] [a b]))
(pr-str (describe (->R 1 2)))"
),
"[1 2]"
);
}
#[test]
fn field_and_this_lookup_agree() {
// The synthesised binding is `(:x this)`, so both spellings must match.
assert_eq!(
eval_pr(
"(defprotocol P (both [this]))
(defrecord R [x] P (both [this] [x (:x this)]))
(pr-str (both (->R :v)))"
),
"[:v :v]"
);
}
#[test]
fn fields_are_read_off_the_instance_not_captured_at_definition() {
// assoc returns a NEW record; the method must see the updated value, which
// it only does because the field is bound from `this` per call.
assert_eq!(
eval_pr(
"(defprotocol P (describe [this]))
(defrecord R [a b] P (describe [_] [a b]))
(pr-str (describe (assoc (->R 1 2) :a 9)))"
),
"[9 2]"
);
}
#[test]
fn a_record_with_no_fields_still_works() {
// `synth_field_scope` returns None here — no `let*` wrapper is synthesised.
assert_eq!(
eval_pr(
"(defprotocol P (tag [this]))
(defrecord R [] P (tag [_] :ok))
(pr-str (tag (->R)))"
),
":ok"
);
}
// ── A param shadows a field of the same name ─────────────────────────────────
#[test]
fn a_param_shadows_the_field_it_shares_a_name_with() {
assert_eq!(
eval_pr(
"(defprotocol P (pick [this a]))
(defrecord R [a] P (pick [_ a] a))
(pr-str (pick (->R :field) :param))"
),
":param"
);
}
#[test]
fn shadowing_one_field_leaves_the_others_bound() {
assert_eq!(
eval_pr(
"(defprotocol P (pick [this a]))
(defrecord R [a b] P (pick [_ a] [a b]))
(pr-str (pick (->R :field :other) :param))"
),
"[:param :other]"
);
}
// ── Forms that must NOT get a field scope ────────────────────────────────────
#[test]
fn reify_closes_over_its_environment_and_gains_no_fields() {
assert_eq!(
eval_pr(
"(defprotocol P (describe [this]))
(pr-str (let [x 42] (describe (reify P (describe [_] x)))))"
),
"42"
);
}
#[test]
fn extend_type_still_reads_fields_through_this() {
assert_eq!(
eval_pr(
"(defprotocol P (describe [this]))
(defrecord R [a b])
(extend-type R P (describe [s] [(:a s) (:b s)]))
(pr-str (describe (->R 1 2)))"
),
"[1 2]"
);
}