Skip to main content

cove_sema/
unique.rs

1//! The conservative local uniqueness proof `Vector.freeze()` needs.
2//!
3//! [ADR 0001](../../../docs/adr/0001-mvp-language-design.md) has said from
4//! the beginning what this pass is:
5//!
6//! > `Vector.freeze()` consumes a vector with uniquely owned storage and
7//! > returns an `Array<T>` in O(1). **The compiler only performs
8//! > conservative, local uniqueness checking for this explicit transition.**
9//! > If uniqueness cannot be proved, `toArray()` creates an independent O(n)
10//! > immutable array.
11//!
12//! Until [issue #240](https://github.com/myuon/cove/issues/240) that sentence
13//! described nobody: the tree-walking interpreter counted `Rc` handles at run
14//! time and refused there, which is a different thing with a different
15//! failure time and a different blast radius. It also cannot be carried
16//! anywhere else. A handle in the linear-memory machine is a word, words are
17//! not counted, and the sharing bit that could have carried the answer went
18//! out with the copy-on-write design. So the choice was to give up the O(1)
19//! transition or to establish uniqueness where the language always said it
20//! was established — in the compiler — and this is the second one.
21//!
22//! # What is proved
23//!
24//! For one `freeze()` call, that the vector it consumes is reached through
25//! exactly one place, and that the place is not read again afterwards. The
26//! four conditions are #240's own list:
27//!
28//! - it **originates at a locally known creation** — `Vector.of(...)`,
29//!   `array.toVector()`, `vector.snapshot()`, or a field initialised by one
30//!   in a struct literal this body wrote;
31//! - it has **not been copied to another live place** — no `let`/`var` binds
32//!   it, no assignment writes it anywhere else;
33//! - it has **not escaped** — no closure captures it, no `return` carries it
34//!   out, nothing stores it in another value, and no call it is passed to can
35//!   keep it;
36//! - it is **consumed** by the `freeze()` and **not used afterward**.
37//!
38//! One more condition is not on that list and belongs to a static pass rather
39//! than a dynamic one: the site must not be somewhere that **runs twice**. A
40//! `freeze()` in a loop body or a closure body, over storage created outside
41//! it, would consume on the first turn what the second turn would find gone,
42//! so the binding has to be created inside the same region the site is in.
43//!
44//! # What is deliberately not treated as an escape, and why each is safe
45//!
46//! Four positions read a place without keeping the handle. Treating them as
47//! escapes would refuse most of the corpus for nothing:
48//!
49//! - **a method call's receiver.** `items.push(n)` and `items.length()` write
50//!   and read through the handle; neither stores it. The exception is a
51//!   *declared* method whose result can reach a `Vector`, which may be a
52//!   getter handing the field back — that is an escape.
53//! - **a string interpolation operand.** `"{items}"` formats the value and
54//!   keeps nothing.
55//! - **a `for` loop's iterable.** Iterating reads elements out; the sequence
56//!   is not retained.
57//! - **a by-value call argument**, when the call has no way to keep it. This
58//!   is the one that needs an argument rather than an observation, and the
59//!   argument is the language's own: a `Vector` is not task-safe, so it
60//!   cannot be put in a `Shared` or carried into a `spawn`, and the Host API
61//!   boundary materialises a `Value`, so a host cannot hold one either. The
62//!   ways out of a callee are therefore its result, a `var` parameter, and
63//!   another operand it could write into. So `firstFree(seed, w, h, cells)`
64//!   — whose result is an `Int` and whose other operands hold no vector —
65//!   keeps nothing, and `into.push(cells)` does. A call whose result can
66//!   reach a `Vector`, a call with a `var` argument, and a call where some
67//!   *other* operand can reach a `Vector` are all escapes.
68//!
69//! # The one obligation that crosses a call
70//!
71//! A builder's `finish` is the shape that made a purely intraprocedural pass
72//! insufficient:
73//!
74//! ```cove
75//! fn finish(var self) -> Router {
76//!   Router(routes: self.routes.freeze())
77//! }
78//! ```
79//!
80//! `self.routes` does not originate here, so this body cannot prove anything
81//! about it — and refusing would refuse `examples/values` and
82//! `examples/callbacks`, both of which are demonstrating the language's own
83//! rule. What the body *can* do is state the condition it needs and make its
84//! callers prove it. So a method that freezes a path rooted at `var self`
85//! becomes a method that **demands a uniquely owned receiver**, and every
86//! call to it is checked by the same local proof, on the receiver place, at
87//! the call site. `fresh.finish()` on a draft this body built passes;
88//! `original.finish()` after `var alias = original` does not.
89//!
90//! The demand is deliberately narrow — a `var self` receiver of a method
91//! written in a plain `impl` block, and nothing else. That is the only
92//! declaration form whose every call site the checker resolves precisely
93//! ([`Facts::target`]), so it is the only one where the obligation cannot be
94//! lost. A `freeze()` rooted at an ordinary parameter, at a captured name, or
95//! at the receiver of a trait method is refused rather than propagated,
96//! because a call through a bound or through `dyn` names no declaration and
97//! there would be nowhere to discharge it.
98//!
99//! There is one way an obligation can be lost, and it is worth naming rather
100//! than leaving implicit: a call whose receiver type the checker declined to
101//! settle records no target, so a `finish()` reached through a value of
102//! unknown type is not checked. That needs a receiver the checker abstained
103//! about — a Host API result a schema declared `Any` — reaching a method of a
104//! declared type, and no program in the corpus does it. Closing it would mean
105//! refusing every unresolved call that shares a name with a demanding method,
106//! which is a diagnostic about a coincidence of names; it is left open, and
107//! written down here, until a program asks for it.
108//!
109//! # What it refuses that the oracle admits
110//!
111//! Conservative means this list is not empty, and the diagnostic's job is to
112//! make each entry legible rather than mysterious. The ones that showed up
113//! while this was written:
114//!
115//! - **storage a call produced.** `var log = freshVector()` then
116//!   `log.freeze()` is refused: the initialiser is a call, and whether its
117//!   answer is fresh is a fact about another body. Proving it would be an
118//!   "answers unaliased storage" summary, which nothing in the corpus asks
119//!   for yet.
120//! - **storage an assignment brought in.** `log = lines` gives `log` whatever
121//!   the caller is holding, and this refuses `log.freeze()` afterwards —
122//!   correctly, in that case.
123//! - **a `var` parameter that is not `self`.** The obligation only travels
124//!   back through a method receiver, so `fn take(var v: Vector<Int>) { ... v.freeze() }`
125//!   is refused.
126//! - **a name a pattern or a `for` bound.** `match maybe { Some(v) => v.freeze() }`
127//!   has no creation to point at.
128//!
129//! Every one of them has the same correction, and the diagnostic gives it:
130//! `toArray()`, which copies in O(n) and asks nothing.
131//!
132//! # This is not a borrow checker
133//!
134//! It answers one question about one method. There is no sharing bit, no
135//! reference count, no copy-on-write and no runtime table; a program that
136//! this pass cannot prove is not a program that is wrong, it is a program
137//! that pays `toArray()`'s O(n) copy instead. The diagnostic says so, and
138//! naming the alias that defeated the proof is most of what it is for.
139
140use std::collections::{BTreeMap, BTreeSet};
141
142use cove_diag::{Diagnostic, FileId, Span};
143use cove_syntax::ast::{
144    Arg, Block, Expr, ExprKind, Ident, ItemKind, Param, Pattern, PatternKind, StmtKind, StrPart,
145    Type, TypeKind,
146};
147
148use crate::facts::Facts;
149use crate::resolve::Program;
150use crate::typeck::Ty;
151
152/// A `freeze()` whose receiver's storage could not be proved uniquely owned.
153pub const NOT_UNIQUE: &str = "cove::unique::not_unique";
154
155/// A read of a vector a `freeze()` has already consumed.
156pub const USED_AFTER_FREEZE: &str = "cove::unique::used_after_freeze";
157
158/// The one sentence this pass enforces, on every diagnostic it raises.
159const RULE: &str =
160    "`freeze()` consumes a vector whose storage the compiler can prove is uniquely owned here, \
161     and returns an immutable array in O(1).";
162
163/// One declaration, as the demand table names it.
164type FnKey = (String, Option<String>, String);
165
166/// A place: the binding it is rooted at and the fields read off it.
167///
168/// `self.guests` is `{ root: "self", binding: None, fields: ["guests"] }`.
169/// `binding` is the index of the `let`, `var` or pattern that introduced the
170/// root, which is what tells two `var parts` in two match arms apart; a root
171/// this body did not bind — a parameter, `self`, a declaration of the module
172/// — has none, and is compared by name.
173///
174/// Two places overlap when one is a prefix of the other, which is exactly
175/// when writing through either is observable through the other.
176#[derive(Clone, Debug, PartialEq, Eq)]
177struct Place {
178    root: String,
179    binding: Option<usize>,
180    fields: Vec<String>,
181}
182
183impl Place {
184    fn overlaps(&self, other: &Place) -> bool {
185        self.binding == other.binding
186            && self.root == other.root
187            && self.fields.iter().zip(&other.fields).all(|(a, b)| a == b)
188    }
189
190    /// The place as a reader wrote it.
191    fn text(&self) -> String {
192        let mut out = self.root.clone();
193        for field in &self.fields {
194            out.push('.');
195            out.push_str(field);
196        }
197        out
198    }
199}
200
201/// One read of a place inside a body.
202#[derive(Clone, Debug)]
203struct Read {
204    place: Place,
205    span: Span,
206    /// What the reader should be told this position was, when the handle
207    /// outlives the expression that read it.
208    retained: Option<&'static str>,
209    /// How many closure bodies deep the read is. A read deeper than its
210    /// binding's own depth is a capture, whatever position it is in.
211    depth: usize,
212}
213
214/// A binding this body introduces.
215#[derive(Clone, Debug)]
216struct Local<'a> {
217    name: &'a str,
218    span: Span,
219    /// The initialiser of a `let` or `var`. A `for` binding, a match arm's
220    /// pattern and a closure parameter have none: what they name came from
221    /// somewhere this body cannot see the creation of.
222    init: Option<&'a Expr>,
223    /// The loop and closure bodies this binding sits inside.
224    regions: Vec<Span>,
225    depth: usize,
226}
227
228/// Something that consumes a place: a `freeze()`, or a call to a method that
229/// demands a uniquely owned receiver.
230#[derive(Clone, Debug)]
231struct Consume {
232    place: Place,
233    /// The whole expression, which is what a diagnostic points at.
234    span: Span,
235    /// The loop and closure bodies this site sits inside.
236    regions: Vec<Span>,
237    /// Whether this site is the operand of a `return`, so that nothing
238    /// written after it in the source runs after it.
239    terminal: bool,
240    /// `None` for a `freeze()`; the callee, for a demanded receiver.
241    through: Option<String>,
242}
243
244/// A call this body makes to a method written in an `impl` block.
245#[derive(Clone, Debug)]
246struct MethodCall {
247    target: FnKey,
248    /// The receiver place, when the receiver is one.
249    receiver: Option<Place>,
250    span: Span,
251    regions: Vec<Span>,
252    terminal: bool,
253}
254
255/// Everything one body says about places.
256#[derive(Default)]
257struct Scan<'a> {
258    locals: Vec<Local<'a>>,
259    reads: Vec<Read>,
260    /// Places an assignment writes, and where.
261    writes: Vec<(Place, Span)>,
262    freezes: Vec<Consume>,
263    calls: Vec<MethodCall>,
264}
265
266/// One body to analyse.
267struct Body<'a> {
268    key: Option<FnKey>,
269    file: FileId,
270    /// Parameter names, receiver excluded.
271    params: Vec<&'a str>,
272    /// `Some(is_var)` when this body has a receiver.
273    receiver: Option<bool>,
274    /// Whether a `var self` here can carry an obligation back to its callers:
275    /// a method of a plain `impl` block, whose every call site the checker
276    /// resolves to this declaration by name.
277    receiver_may_demand: bool,
278    block: &'a Block,
279}
280
281/// The named types whose values can reach a `Vector`.
282///
283/// A struct or enum is one when a field or a payload names a `Vector`, or
284/// names another such type — so `World`, whose fields are `Array`s, is not,
285/// and `BookingDraft`, whose `guests` is a `Vector`, is. Keyed by the type's
286/// own name without its module, because two modules' types of one name are
287/// merged here and merging in this direction only ever refuses more.
288type Bearing = BTreeSet<String>;
289
290/// Checks every `freeze()` in `program`.
291///
292/// The answer is one diagnostic per site that could not be proved, and one
293/// per read of a vector a proved site already consumed.
294pub fn check(program: &Program, facts: &Facts) -> Vec<Diagnostic> {
295    let bearing = vector_bearing(program);
296    let bodies = bodies(program);
297    let scans: Vec<Scan<'_>> = bodies
298        .iter()
299        .map(|body| scan(body, facts, &bearing))
300        .collect();
301
302    // Which methods demand a uniquely owned receiver, to a fixpoint: a
303    // `finish` that freezes `self.routes` demands `routes`, and a method that
304    // calls `finish` on `self.builder` demands `builder.routes` in turn.
305    let mut demands: BTreeMap<FnKey, BTreeSet<Vec<String>>> = BTreeMap::new();
306    loop {
307        let mut changed = false;
308        for (body, scanned) in bodies.iter().zip(&scans) {
309            let (Some(key), Some(true), true) =
310                (&body.key, body.receiver, body.receiver_may_demand)
311            else {
312                continue;
313            };
314            for consumed in consumptions(scanned, &demands) {
315                if consumed.place.root != "self" || consumed.place.binding.is_some() {
316                    continue;
317                }
318                changed |= demands
319                    .entry(key.clone())
320                    .or_default()
321                    .insert(consumed.place.fields.clone());
322            }
323        }
324        if !changed {
325            break;
326        }
327    }
328
329    let mut diagnostics = Vec::new();
330    for (body, scanned) in bodies.iter().zip(&scans) {
331        for consumed in consumptions(scanned, &demands) {
332            prove(body, scanned, facts, &demands, &consumed, &mut diagnostics);
333        }
334    }
335    diagnostics.sort_by_key(|diagnostic| {
336        diagnostic
337            .primary
338            .map(|span| (span.file.0, span.start))
339            .unwrap_or((u32::MAX, u32::MAX))
340    });
341    diagnostics
342}
343
344/// Every place a body consumes: its `freeze()` sites, and every call to a
345/// method that demands a uniquely owned receiver.
346fn consumptions(
347    scanned: &Scan<'_>,
348    demands: &BTreeMap<FnKey, BTreeSet<Vec<String>>>,
349) -> Vec<Consume> {
350    let mut out = scanned.freezes.clone();
351    for call in &scanned.calls {
352        let (Some(paths), Some(receiver)) = (demands.get(&call.target), &call.receiver) else {
353            continue;
354        };
355        for fields in paths {
356            let mut place = receiver.clone();
357            place.fields.extend(fields.iter().cloned());
358            out.push(Consume {
359                place,
360                span: call.span,
361                regions: call.regions.clone(),
362                terminal: call.terminal,
363                through: Some(format!(
364                    "{}.{}",
365                    call.target.1.clone().unwrap_or_default(),
366                    call.target.2
367                )),
368            });
369        }
370    }
371    out
372}
373
374// --- the proof -------------------------------------------------------------
375
376/// Proves one consumption, or says what defeated it.
377fn prove(
378    body: &Body<'_>,
379    scanned: &Scan<'_>,
380    facts: &Facts,
381    demands: &BTreeMap<FnKey, BTreeSet<Vec<String>>>,
382    consumed: &Consume,
383    out: &mut Vec<Diagnostic>,
384) {
385    let place = consumed.place.text();
386    let opening = match &consumed.through {
387        None => {
388            format!("`freeze()` cannot prove that `{place}` holds the only handle to its storage")
389        }
390        Some(callee) => format!(
391            "`{callee}()` consumes `{place}`, and this call cannot prove that it holds the only \
392             handle to its storage"
393        ),
394    };
395    let refuse = |span: Span, message: String, out: &mut Vec<Diagnostic>| {
396        out.push(
397            Diagnostic::error(NOT_UNIQUE, opening.clone())
398                .at(consumed.span)
399                .label(span, message)
400                .rule(RULE)
401                .help(
402                    "call `toArray()` on the vector instead, which copies the elements in O(n) \
403                     and asks nothing about who else is holding it",
404                ),
405        );
406    };
407
408    // Where the root comes from. A binding this body made is provable here; a
409    // `var self` that a plain `impl` method received carries the obligation on
410    // to its callers; anything else is outside what a local pass can see.
411    let (declared, depth) = match consumed.place.binding.map(|at| &scanned.locals[at]) {
412        Some(local) => {
413            let Some(init) = local.init else {
414                refuse(
415                    local.span,
416                    format!(
417                        "`{}` is bound to a value this function did not create, so its storage \
418                         may already have another handle",
419                        local.name
420                    ),
421                    out,
422                );
423                return;
424            };
425            if !establishes(init, &consumed.place.fields, facts, body.file) {
426                refuse(
427                    init.span,
428                    format!(
429                        "`{}` is initialised from a value this function did not create, so its \
430                         storage may already have another handle",
431                        consumed.place.text()
432                    ),
433                    out,
434                );
435                return;
436            }
437            (local.regions.clone(), local.depth)
438        }
439        None => {
440            let carried = consumed.place.root == "self"
441                && body.receiver == Some(true)
442                && body.receiver_may_demand
443                && body
444                    .key
445                    .as_ref()
446                    .and_then(|key| demands.get(key))
447                    .is_some_and(|paths| paths.contains(&consumed.place.fields));
448            if !carried {
449                let from_caller = body.params.contains(&consumed.place.root.as_str())
450                    || (consumed.place.root == "self" && body.receiver.is_some());
451                refuse(
452                    consumed.span,
453                    if from_caller {
454                        format!(
455                            "`{}` comes from this function's caller, and only a `var self` \
456                             receiver of a method written in a plain `impl` block can carry the \
457                             obligation back to it",
458                            consumed.place.root
459                        )
460                    } else {
461                        format!(
462                            "`{}` is not a binding this function creates, so where its storage \
463                             came from is not a local fact",
464                            consumed.place.root
465                        )
466                    },
467                    out,
468                );
469                return;
470            }
471            // The obligation left this body for its call sites, which this
472            // same pass checks. What stays here is that the body itself
473            // neither copies the handle nor reads it afterwards.
474            (Vec::new(), 0)
475        }
476    };
477
478    // A site inside a loop or a closure body runs more than once unless the
479    // storage is created inside it too, and a second turn would take storage
480    // the first already took.
481    if let Some(repeated) = consumed
482        .regions
483        .iter()
484        .find(|span| !declared.contains(span))
485    {
486        refuse(
487            *repeated,
488            "this may run more than once, and a second turn would consume storage the first one \
489             already took"
490                .to_string(),
491            out,
492        );
493        return;
494    }
495
496    // Written somewhere else: the place no longer names what its initialiser
497    // created.
498    if let Some((written, span)) = scanned
499        .writes
500        .iter()
501        .find(|(written, _)| written.overlaps(&consumed.place))
502    {
503        refuse(
504            *span,
505            format!(
506                "`{}` is assigned here, so the storage it names at the consumption is not the \
507                 storage it was created with",
508                written.text()
509            ),
510            out,
511        );
512        return;
513    }
514
515    // Copied to another live place, or escaped.
516    if let Some((read, why)) = scanned.reads.iter().find_map(|read| {
517        if !read.place.overlaps(&consumed.place) || read.span == consumed.span {
518            return None;
519        }
520        if read.depth > depth {
521            return Some((read, "is captured by a closure"));
522        }
523        read.retained.map(|why| (read, why))
524    }) {
525        refuse(
526            read.span,
527            format!("`{}` {why} here", read.place.text()),
528            out,
529        );
530        return;
531    }
532
533    // Consumed, and therefore not usable afterward. A site a `return` carries
534    // out of the function has nothing after it to check.
535    if consumed.terminal {
536        return;
537    }
538    for read in &scanned.reads {
539        if read.place.overlaps(&consumed.place) && read.span.start >= consumed.span.end {
540            out.push(
541                Diagnostic::error(
542                    USED_AFTER_FREEZE,
543                    format!(
544                        "`{}` is read after its storage was consumed",
545                        read.place.text()
546                    ),
547                )
548                .at(read.span)
549                .label(
550                    consumed.span,
551                    match &consumed.through {
552                        None => "`freeze()` took the storage here".to_string(),
553                        Some(callee) => format!("`{callee}()` took the storage here"),
554                    },
555                )
556                .rule(RULE)
557                .help(
558                    "read the `Array` the transition answered, or call `toArray()` instead, which \
559                     copies the elements in O(n) and leaves the vector usable",
560                ),
561            );
562        }
563    }
564}
565
566/// Whether `init` creates storage this body is the only holder of, reached
567/// through `fields`.
568///
569/// With no fields, the initialiser has to be a call [`creates`] recognises.
570/// With fields, the initialiser has to be a literal this body wrote, so that
571/// the named field's own initialiser can be asked the same question.
572fn establishes(init: &Expr, fields: &[String], facts: &Facts, file: FileId) -> bool {
573    let Some((first, rest)) = fields.split_first() else {
574        return creates(init, facts, file);
575    };
576    match &init.kind {
577        ExprKind::Call { args, .. } => args
578            .iter()
579            .find(|arg| arg.label.as_ref().is_some_and(|label| label.node == *first))
580            .is_some_and(|arg| establishes(&arg.value, rest, facts, file)),
581        _ => false,
582    }
583}
584
585/// Whether this expression allocates a vector nothing else holds a handle to.
586///
587/// # Freshness is a fact `cove-schema` states, not a name this pass matches
588///
589/// The answer used to be three method names matched against the source
590/// regardless of what they were called on — `of`, `toVector`, `snapshot` —
591/// which is exactly the coupling
592/// [issue #270](https://github.com/myuon/cove/issues/270) named: a fourth
593/// builtin that answers a fresh `Vector` needed a fourth name added here, and
594/// a call that merely *shared* one of these three names — a user's own
595/// `snapshot()` on an unrelated type, reached through a value of that type —
596/// would have been read as fresh too, because nothing here ever asked what
597/// `base` actually was.
598///
599/// Now the call has to *resolve* to a builtin entry `cove-schema` marks
600/// [`MethodSchema::fresh`](cove_schema::builtins::MethodSchema::fresh), and
601/// resolving it is what tells the two call shapes apart:
602///
603/// - `Vector.of(...)`: an associated function, named through the type
604///   itself. There is no value to type — [`Facts::ty`] answers `None` for
605///   `base` for exactly this reason (see the `facts` module) — so `base`
606///   is read as a builtin type's own name instead, the same name
607///   `cove_schema::is_builtin_type` uses to admit `Vector.of(...)` in the
608///   first place.
609/// - `array.toVector()`, `vector.snapshot()`: a method, named through a
610///   receiver whose type the checker already settled and recorded. That
611///   type is read off [`Facts::ty`] and turned into the builtin schema it
612///   names, so `array.snapshot()` and `vector.snapshot()` are answered from
613///   two different entries even though the source spells the call the same
614///   way.
615///
616/// A call that resolves to neither — a declared function, a method of a
617/// declared type, or a builtin entry the schema does not mark `fresh` —
618/// answers `false`. That includes a call to a Cove-written wrapper such as
619/// `std.vector.filter`, whose own `out.freeze()` this pass proves the
620/// ordinary way from the `Vector.of()` a few lines above it in the same
621/// body: a *caller* of `filter` never reaches this function at all, because
622/// `filter`'s result is not the direct initialiser of anything `creates`
623/// looks at. See `MethodSchema::fresh` for who may assert freshness and why
624/// a declared `fn` is not on that list.
625fn creates(init: &Expr, facts: &Facts, file: FileId) -> bool {
626    let ExprKind::Call { callee, .. } = &init.kind else {
627        return false;
628    };
629    let ExprKind::Field { base, name } = &callee.kind else {
630        return false;
631    };
632    if let ExprKind::Ident(head) = &base.kind {
633        if facts.ty(file, base.id).is_none() {
634            if let Some(schema) = cove_schema::builtin(head) {
635                return schema
636                    .associated_function(&name.node)
637                    .is_some_and(|method| method.fresh);
638            }
639        }
640    }
641    facts
642        .ty(file, base.id)
643        .and_then(crate::typeck::builtin_schema_of)
644        .and_then(|schema| schema.method(&name.node))
645        .is_some_and(|method| method.fresh)
646}
647
648// --- reading a body --------------------------------------------------------
649
650/// The places one body reads, writes, creates and consumes.
651fn scan<'a>(body: &Body<'a>, facts: &Facts, bearing: &Bearing) -> Scan<'a> {
652    let mut walk = Walk {
653        facts,
654        bearing,
655        file: body.file,
656        regions: Vec::new(),
657        depth: 0,
658        terminal: false,
659        scopes: vec![Vec::new()],
660        scan: Scan::default(),
661    };
662    walk.block(body.block, None);
663    walk.scan
664}
665
666/// A walk of one body, carrying where it is.
667struct Walk<'a, 'f> {
668    facts: &'f Facts,
669    bearing: &'f Bearing,
670    file: FileId,
671    /// The loop and closure bodies enclosing the expression being walked.
672    regions: Vec<Span>,
673    depth: usize,
674    /// Whether what is being walked is carried out of the function by a
675    /// `return`.
676    terminal: bool,
677    /// Names in scope, innermost last, each naming a `Scan::locals` index.
678    scopes: Vec<Vec<(&'a str, usize)>>,
679    scan: Scan<'a>,
680}
681
682impl<'a> Walk<'a, '_> {
683    /// Runs `body` with a scope of its own.
684    fn scoped(&mut self, body: impl FnOnce(&mut Self)) {
685        self.scopes.push(Vec::new());
686        body(self);
687        self.scopes.pop();
688    }
689
690    /// Introduces a binding into the innermost scope.
691    fn bind(&mut self, name: &'a str, span: Span, init: Option<&'a Expr>) {
692        self.scan.locals.push(Local {
693            name,
694            span,
695            init,
696            regions: self.regions.clone(),
697            depth: self.depth,
698        });
699        let at = self.scan.locals.len() - 1;
700        self.scopes
701            .last_mut()
702            .expect("a body is walked inside a scope")
703            .push((name, at));
704    }
705
706    /// The binding `name` resolves to here, if this body made one.
707    fn resolve(&self, name: &str) -> Option<usize> {
708        self.scopes
709            .iter()
710            .rev()
711            .find_map(|scope| scope.iter().rev().find(|(bound, _)| *bound == name))
712            .map(|(_, at)| *at)
713    }
714
715    /// The place an expression names, when it names one.
716    fn place_of(&self, expr: &Expr) -> Option<Place> {
717        match &expr.kind {
718            ExprKind::Ident(name) => Some(Place {
719                root: name.clone(),
720                binding: self.resolve(name),
721                fields: Vec::new(),
722            }),
723            ExprKind::Field { base, name } => {
724                let mut place = self.place_of(base)?;
725                place.fields.push(name.node.clone());
726                Some(place)
727            }
728            _ => None,
729        }
730    }
731
732    /// `retain` is `Some(why)` when the value this position produces outlives
733    /// the expression that produced it.
734    fn block(&mut self, block: &'a Block, retain: Option<&'static str>) {
735        self.scoped(|walk| {
736            for stmt in &block.statements {
737                match &stmt.kind {
738                    StmtKind::Let { name, value, .. } => {
739                        walk.expr(value, Some("is copied into another binding"));
740                        walk.bind(&name.node, name.span, Some(value));
741                    }
742                    StmtKind::Expr(expr) => walk.expr(expr, None),
743                    // A local `fn` is a closure the body can call, so its own
744                    // body is walked as one.
745                    StmtKind::Item(item) => {
746                        if let ItemKind::Fn(decl) = &item.kind {
747                            walk.nested(&decl.params, &decl.body);
748                        }
749                    }
750                }
751            }
752            if let Some(tail) = &block.tail {
753                walk.expr(tail, retain);
754            }
755        });
756    }
757
758    /// A closure body: a region that may run more than once, and whose reads
759    /// of an outer binding are captures.
760    fn nested(&mut self, params: &'a [Param], block: &'a Block) {
761        self.regions.push(block.span);
762        self.depth += 1;
763        let terminal = std::mem::replace(&mut self.terminal, false);
764        self.scoped(|walk| {
765            for param in params {
766                walk.bind(&param.name.node, param.name.span, None);
767            }
768            walk.block(block, None);
769        });
770        self.terminal = terminal;
771        self.depth -= 1;
772        self.regions.pop();
773    }
774
775    /// A loop body, which may run more than once but captures nothing.
776    fn repeated(&mut self, binding: Option<&'a Ident>, block: &'a Block) {
777        self.regions.push(block.span);
778        let terminal = std::mem::replace(&mut self.terminal, false);
779        self.scoped(|walk| {
780            if let Some(binding) = binding {
781                walk.bind(&binding.node, binding.span, None);
782            }
783            walk.block(block, None);
784        });
785        self.terminal = terminal;
786        self.regions.pop();
787    }
788
789    fn expr(&mut self, expr: &'a Expr, retain: Option<&'static str>) {
790        // A place is read whole. Its base is part of the path rather than a
791        // separate read, so the walk stops here.
792        if let Some(place) = self.place_of(expr) {
793            self.scan.reads.push(Read {
794                place,
795                span: expr.span,
796                retained: retain,
797                depth: self.depth,
798            });
799            return;
800        }
801        match &expr.kind {
802            ExprKind::Call {
803                callee,
804                args,
805                trailing,
806                ..
807            } => self.call(expr, callee, args, trailing.as_deref()),
808            // Reached only when the base is not a place, as in `f().x`.
809            ExprKind::Field { base, .. } => self.expr(base, None),
810            ExprKind::ArrayLit(items) => {
811                for item in items {
812                    self.expr(item, Some("is stored in another value"));
813                }
814            }
815            // An interpolation formats its operand and keeps nothing.
816            ExprKind::Str(parts) => {
817                for part in parts {
818                    if let StrPart::Interpolation(inner) = part {
819                        self.expr(inner, None);
820                    }
821                }
822            }
823            ExprKind::Unary { operand, .. } => self.expr(operand, None),
824            ExprKind::Binary { lhs, rhs, .. } => {
825                self.expr(lhs, None);
826                self.expr(rhs, None);
827            }
828            ExprKind::Assign { target, value, .. } => {
829                match self.place_of(target) {
830                    Some(place) => self.scan.writes.push((place, expr.span)),
831                    None => self.expr(target, None),
832                }
833                self.expr(value, Some("is copied into another place"));
834            }
835            ExprKind::Try(inner) | ExprKind::Await(inner) => self.expr(inner, retain),
836            ExprKind::Block(block) => self.block(block, retain),
837            ExprKind::If {
838                condition,
839                then_branch,
840                else_branch,
841            } => {
842                self.expr(condition, None);
843                self.block(then_branch, retain);
844                if let Some(other) = else_branch {
845                    self.expr(other, retain);
846                }
847            }
848            ExprKind::Match { scrutinee, arms } => {
849                self.expr(scrutinee, None);
850                for arm in arms {
851                    self.scoped(|walk| {
852                        walk.pattern(&arm.pattern);
853                        walk.expr(&arm.body, retain);
854                    });
855                }
856            }
857            // Iterating reads elements out; the sequence is not retained.
858            ExprKind::For {
859                binding,
860                iterable,
861                body,
862            } => {
863                self.expr(iterable, None);
864                self.repeated(Some(binding), body);
865            }
866            ExprKind::While { condition, body } => {
867                self.expr(condition, None);
868                self.repeated(None, body);
869            }
870            ExprKind::Return(Some(value)) => {
871                let outer = std::mem::replace(&mut self.terminal, true);
872                self.expr(value, Some("is returned"));
873                self.terminal = outer;
874            }
875            // A loop is `Unit` however it leaves, so a `break` value is
876            // evaluated and discarded.
877            ExprKind::Break(Some(value)) => self.expr(value, None),
878            ExprKind::Lambda { params, body, .. } => self.nested(params, body),
879            // A scope's body may outlive the statement that wrote it — a
880            // spawned task runs inside it — so it is a closure body here, and
881            // the name it binds is one of its own.
882            ExprKind::Scope { name, body } => {
883                self.regions.push(body.span);
884                self.depth += 1;
885                let terminal = std::mem::replace(&mut self.terminal, false);
886                self.scoped(|walk| {
887                    walk.bind(&name.node, name.span, None);
888                    walk.block(body, None);
889                });
890                self.terminal = terminal;
891                self.depth -= 1;
892                self.regions.pop();
893            }
894            ExprKind::Range { start, end, .. } => {
895                self.expr(start, None);
896                self.expr(end, None);
897            }
898            _ => {}
899        }
900    }
901
902    /// Every name a pattern binds, as a binding this body cannot see the
903    /// creation of.
904    fn pattern(&mut self, pattern: &'a Pattern) {
905        match &pattern.kind {
906            PatternKind::Binding(name) => self.bind(name, pattern.span, None),
907            PatternKind::Variant { payload, .. } => {
908                for inner in payload {
909                    self.pattern(inner);
910                }
911            }
912            PatternKind::Wildcard | PatternKind::Literal(_) => {}
913        }
914    }
915
916    /// A call, and what each of its operands does with the handle it names.
917    ///
918    /// A `Vector` cannot cross a task boundary and cannot be held by a Host
919    /// resource — the Language Card's task-safety rule and ADR 0017's
920    /// boundary see to both — so the handle a callee is passed can only
921    /// outlive the call by leaving through the callee's own result, through a
922    /// `var` argument the caller can see, or by being written into another
923    /// operand that is itself a container. When none of those is possible the
924    /// copy dies with the call, and treating it as an escape would refuse
925    /// `world.firstFree(..., creatures)` for nothing.
926    fn call(
927        &mut self,
928        call: &'a Expr,
929        callee: &'a Expr,
930        args: &'a [Arg],
931        trailing: Option<&'a Expr>,
932    ) {
933        let result_holds = self
934            .facts
935            .ty(self.file, call.id)
936            .is_some_and(|ty| self.holds_vector(ty));
937        let receiver = match &callee.kind {
938            ExprKind::Field { base, name } => {
939                self.method(call, base, &name.node, result_holds);
940                Some(base)
941            }
942            _ => {
943                self.expr(callee, None);
944                None
945            }
946        };
947        // Which operands could be a container the callee writes another
948        // operand into.
949        let containers: Vec<bool> = receiver
950            .into_iter()
951            .map(|base| &**base)
952            .chain(args.iter().map(|arg| &arg.value))
953            .map(|operand| {
954                self.facts
955                    .ty(self.file, operand.id)
956                    .is_some_and(|ty| self.holds_vector(ty))
957            })
958            .collect();
959        let elsewhere = |at: usize| {
960            containers
961                .iter()
962                .enumerate()
963                .any(|(j, held)| *held && j != at)
964        };
965        let offset = usize::from(receiver.is_some());
966        for (at, arg) in args.iter().enumerate() {
967            let inout = args
968                .iter()
969                .enumerate()
970                .any(|(j, other)| other.is_var && j != at);
971            let retained = if result_holds {
972                Some("escapes into a call that may answer with it")
973            } else if inout {
974                Some("escapes into a call that writes through a `var` argument")
975            } else if elsewhere(at + offset) {
976                Some("escapes into a call that may store it in another argument")
977            } else {
978                None
979            };
980            self.expr(&arg.value, retained);
981        }
982        if let Some(trailing) = trailing {
983            self.expr(trailing, Some("is captured by a trailing closure"));
984        }
985    }
986
987    /// A method call's receiver, and whether this call is a consumption.
988    fn method(&mut self, call: &'a Expr, base: &'a Expr, name: &str, result_holds: bool) {
989        let receiver = self.place_of(base);
990        if name == "freeze" && matches!(self.facts.ty(self.file, base.id), Some(Ty::Vector(_))) {
991            if let Some(place) = receiver.clone() {
992                self.scan.freezes.push(Consume {
993                    place,
994                    span: call.span,
995                    regions: self.regions.clone(),
996                    terminal: self.terminal,
997                    through: None,
998                });
999            }
1000        }
1001        let declared = self.facts.target(self.file, call.id);
1002        if let Some(target) = declared {
1003            self.scan.calls.push(MethodCall {
1004                target: (
1005                    target.module.clone(),
1006                    Some(target.type_name.clone()),
1007                    target.method.clone(),
1008                ),
1009                receiver,
1010                span: call.span,
1011                regions: self.regions.clone(),
1012                terminal: self.terminal,
1013            });
1014        }
1015        // A declared method whose result can reach a `Vector` may be handing
1016        // the receiver's own field back, which is a copy of the handle. Every
1017        // builtin that answers one — `snapshot`, `toVector` — answers a fresh
1018        // one, and every other position reads through the receiver without
1019        // keeping it.
1020        self.expr(
1021            base,
1022            (declared.is_some() && result_holds)
1023                .then_some("is handed back by a method called here"),
1024        );
1025    }
1026
1027    /// Whether a value of this type can reach a `Vector`.
1028    fn holds_vector(&self, ty: &Ty) -> bool {
1029        match ty {
1030            Ty::Vector(_) => true,
1031            Ty::Array(inner)
1032            | Ty::Set(inner)
1033            | Ty::Option(inner)
1034            | Ty::Task(inner)
1035            | Ty::Shared(inner) => self.holds_vector(inner),
1036            Ty::Map(key, value) | Ty::MapEntry(key, value) | Ty::Result(key, value) => {
1037                self.holds_vector(key) || self.holds_vector(value)
1038            }
1039            Ty::Struct(name, args) | Ty::Enum(name, args) => {
1040                self.bearing.contains(simple_name(name))
1041                    || args.iter().any(|ty| self.holds_vector(ty))
1042            }
1043            Ty::Fn(signature) => {
1044                signature.params.iter().any(|ty| self.holds_vector(ty))
1045                    || self.holds_vector(&signature.ret)
1046            }
1047            _ => false,
1048        }
1049    }
1050}
1051
1052/// A type's name without the module that qualifies it.
1053fn simple_name(name: &str) -> &str {
1054    name.rsplit('.').next().unwrap_or(name)
1055}
1056
1057/// Every declared type whose values can reach a `Vector`, to a fixpoint.
1058///
1059/// Read off the written types rather than the checked ones, because what is
1060/// wanted is one bit per declaration and the declarations are what the
1061/// package holds. A name is compared without its module for the reason
1062/// [`Bearing`] states.
1063fn vector_bearing(program: &Program) -> Bearing {
1064    let mut members: BTreeMap<&str, Vec<&Type>> = BTreeMap::new();
1065    for module in program.modules.values() {
1066        for (name, entry) in &module.structs {
1067            members
1068                .entry(simple_name(name))
1069                .or_default()
1070                .extend(entry.decl.fields.iter().map(|field| &field.ty));
1071        }
1072        for (name, entry) in &module.enums {
1073            members
1074                .entry(simple_name(name))
1075                .or_default()
1076                .extend(entry.decl.cases.iter().flat_map(|case| case.payload.iter()));
1077        }
1078    }
1079    let mut bearing = Bearing::new();
1080    loop {
1081        let mut changed = false;
1082        for (name, types) in &members {
1083            if bearing.contains(*name) {
1084                continue;
1085            }
1086            if types.iter().any(|ty| names_a_vector(ty, &bearing)) {
1087                bearing.insert((*name).to_string());
1088                changed = true;
1089            }
1090        }
1091        if !changed {
1092            return bearing;
1093        }
1094    }
1095}
1096
1097/// Whether a written type names `Vector`, or a type already known to bear
1098/// one.
1099fn names_a_vector(ty: &Type, bearing: &Bearing) -> bool {
1100    match &ty.kind {
1101        TypeKind::Named { path, args } => {
1102            let name = path
1103                .last()
1104                .map(|segment| segment.node.as_str())
1105                .unwrap_or_default();
1106            name == "Vector"
1107                || bearing.contains(name)
1108                || args.iter().any(|arg| names_a_vector(arg, bearing))
1109        }
1110        TypeKind::Fn {
1111            params,
1112            return_type,
1113            ..
1114        } => {
1115            params.iter().any(|param| {
1116                param
1117                    .ty
1118                    .as_ref()
1119                    .is_some_and(|ty| names_a_vector(ty, bearing))
1120            }) || return_type
1121                .as_ref()
1122                .is_some_and(|ty| names_a_vector(ty, bearing))
1123        }
1124        TypeKind::Dyn(_) | TypeKind::Unit => false,
1125    }
1126}
1127
1128/// Every body of the package, in one list.
1129///
1130/// A trait's default body is here once, where the trait declares it, exactly
1131/// as the type checker walks it — a conformance that inherits one does not
1132/// get a copy.
1133fn bodies(program: &Program) -> Vec<Body<'_>> {
1134    // A method name any trait declares can be reached through a bound or
1135    // through `dyn`, and neither names a declaration for an obligation to be
1136    // discharged at. Such a method may freeze what it creates, and may not
1137    // demand anything of its callers.
1138    let through_a_trait: BTreeSet<&str> = program
1139        .modules
1140        .values()
1141        .flat_map(|module| module.traits.values())
1142        .flat_map(|entry| entry.decl.methods.iter())
1143        .map(|method| method.name.node.as_str())
1144        .collect();
1145
1146    let mut out = Vec::new();
1147    for (module_name, module) in &program.modules {
1148        for (name, entry) in &module.functions {
1149            out.push(Body {
1150                key: Some((module_name.clone(), None, name.clone())),
1151                file: entry.decl.span.file,
1152                params: names(&entry.decl.params),
1153                receiver: entry.decl.receiver.map(|receiver| receiver.is_var),
1154                receiver_may_demand: false,
1155                block: &entry.decl.body,
1156            });
1157        }
1158        for ((type_name, name), entry) in &module.methods {
1159            if entry.from_trait_default.is_some() {
1160                continue;
1161            }
1162            out.push(Body {
1163                key: Some((module_name.clone(), Some(type_name.clone()), name.clone())),
1164                file: entry.decl.span.file,
1165                params: names(&entry.decl.params),
1166                receiver: entry.decl.receiver.map(|receiver| receiver.is_var),
1167                receiver_may_demand: !through_a_trait.contains(name.as_str()),
1168                block: &entry.decl.body,
1169            });
1170        }
1171        for entry in module.traits.values() {
1172            for method in &entry.decl.methods {
1173                let Some(default) = &method.default else {
1174                    continue;
1175                };
1176                out.push(Body {
1177                    key: None,
1178                    file: method.span.file,
1179                    params: names(&method.params),
1180                    receiver: method.receiver.map(|receiver| receiver.is_var),
1181                    receiver_may_demand: false,
1182                    block: default,
1183                });
1184            }
1185        }
1186    }
1187    out
1188}
1189
1190/// The names of a declaration's parameters, in order.
1191fn names(params: &[Param]) -> Vec<&str> {
1192    params
1193        .iter()
1194        .map(|param| param.name.node.as_str())
1195        .collect()
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200    use std::collections::BTreeMap;
1201    use std::path::PathBuf;
1202
1203    use cove_diag::SourceMap;
1204
1205    use super::*;
1206    use crate::package::{Module, Package, Unit};
1207    use crate::typeck::check;
1208
1209    /// Everything `cove check` reports about one module.
1210    fn errors_of(source: &str) -> Vec<Diagnostic> {
1211        let mut sources = SourceMap::new();
1212        let path = PathBuf::from("main.cove");
1213        let file = sources.add(path.clone(), source);
1214        let ast = cove_syntax::parse_file(&sources, file).expect("test source parses");
1215        let package = Package {
1216            root: PathBuf::new(),
1217            config: crate::config::Config::default(),
1218            modules: BTreeMap::from([(
1219                "main".to_string(),
1220                Module {
1221                    name: "main".to_string(),
1222                    dir: PathBuf::from("main"),
1223                    units: vec![Unit { file, path, ast }],
1224                },
1225            )]),
1226        };
1227        let program = crate::resolve::resolve(&package).expect("test source resolves");
1228        check(&package, &program)
1229            .into_iter()
1230            .filter(|diagnostic| diagnostic.severity == cove_diag::Severity::Error)
1231            .collect()
1232    }
1233
1234    #[track_caller]
1235    fn proves(source: &str) {
1236        let errors = errors_of(source);
1237        assert!(
1238            errors.is_empty(),
1239            "expected the proof to succeed, found: {}",
1240            errors
1241                .iter()
1242                .map(|error| format!("{}: {}", error.code, error.message))
1243                .collect::<Vec<_>>()
1244                .join("; ")
1245        );
1246    }
1247
1248    #[track_caller]
1249    fn refuses(source: &str) -> Diagnostic {
1250        let mut errors = errors_of(source);
1251        assert_eq!(
1252            errors.len(),
1253            1,
1254            "expected exactly one error, found: {}",
1255            errors
1256                .iter()
1257                .map(|error| format!("{}: {}", error.code, error.message))
1258                .collect::<Vec<_>>()
1259                .join("; ")
1260        );
1261        errors.remove(0)
1262    }
1263
1264    /// The shape `freeze()` was written for: build a vector, hand it over.
1265    #[test]
1266    fn a_vector_built_here_and_handed_over_is_proved() {
1267        proves(
1268            "\
1269fn build(upTo: Int) -> Array<Int> {
1270  var building = Vector.of()
1271  for n in 1..upTo {
1272    building.push(n)
1273  }
1274  building.freeze()
1275}
1276",
1277        );
1278    }
1279
1280    /// A temporary holds the only handle to its own storage, so there is
1281    /// nothing to prove and no place to name.
1282    #[test]
1283    fn a_temporary_receiver_needs_no_proof() {
1284        proves("fn build() -> Int {\n  Vector.of(1, 2).freeze().length()\n}\n");
1285        proves("fn build(items: Array<Int>) -> Array<Int> {\n  items.toVector().freeze()\n}\n");
1286    }
1287
1288    /// The case the corpus pins: a second binding is a second handle.
1289    #[test]
1290    fn a_second_binding_defeats_the_proof_and_the_diagnostic_names_it() {
1291        let error = refuses(
1292            "\
1293fn build() -> Array<Int> {
1294  var building = Vector.of(1, 2)
1295  var alias = building
1296  alias.push(3)
1297  building.freeze()
1298}
1299",
1300        );
1301        assert_eq!(error.code, NOT_UNIQUE);
1302        assert_eq!(
1303            error.message,
1304            "`freeze()` cannot prove that `building` holds the only handle to its storage"
1305        );
1306        assert_eq!(
1307            error.labels[0].message,
1308            "`building` is copied into another binding here"
1309        );
1310        assert!(
1311            error
1312                .help
1313                .as_deref()
1314                .is_some_and(|help| help.contains("toArray()")),
1315            "{:?}",
1316            error.help
1317        );
1318    }
1319
1320    /// Formatting a vector and pushing onto it both read through the handle
1321    /// without keeping it, which is what `tests/e2e:coll_array` needs.
1322    #[test]
1323    fn interpolating_and_pushing_are_not_escapes() {
1324        proves(
1325            "\
1326use console.println
1327
1328fn build(items: Array<Int>) -> Result<Array<Int>, Error> {
1329  var growable = items.toVector()
1330  growable.push(40)
1331  println(\"{growable}\")?
1332  Ok(growable.freeze())
1333}
1334",
1335        );
1336    }
1337
1338    /// A callee that cannot keep the handle is not an escape; the three
1339    /// ways one can are.
1340    /// **A vector handed to a call as its own `var` argument is still the
1341    /// caller's afterwards.**
1342    ///
1343    /// A `var` argument is a write-through borrow that ends when the call
1344    /// returns, and the three ways a handle outlives a call are the three
1345    /// this module's `call` documents: the callee's result, another `var`
1346    /// argument the caller can see, or another operand that is a container.
1347    /// For the `var` argument *itself* the second route would mean writing
1348    /// it into itself, which needs a `Vector<T>` whose `T` is that same
1349    /// `Vector<T>` and is not a type Cove can write.
1350    ///
1351    /// This was refused, and the refusal cost the one pattern the language
1352    /// has for building a sequence: fill a `Vector` through a call, then
1353    /// `freeze` it. `examples/covefmt` wrote `"".join(out.toArray())` at nine
1354    /// sites because of it, and `toArray` copies the whole store where
1355    /// `freeze` re-labels it in place.
1356    #[test]
1357    fn a_var_argument_is_not_made_shared_by_being_the_var_argument() {
1358        proves(
1359            "\
1360fn fill(var into: Vector<Int>) {
1361  into.push(1)
1362}
1363
1364fn build() -> Array<Int> {
1365  var items = Vector.of(1, 2)
1366  fill(var items)
1367  items.freeze()
1368}
1369",
1370        );
1371        // A *second* container beside it is the case that still escapes: the
1372        // callee may write that one into this one, and which way round the
1373        // types would allow is not something this asks.
1374        let beside = refuses(
1375            "\
1376fn fill(var into: Vector<Vector<Int>>, from: Vector<Int>) {
1377  into.push(from)
1378}
1379
1380fn build() -> Array<Int> {
1381  var rows = Vector.of<Vector<Int>>()
1382  var items = Vector.of(1, 2)
1383  fill(var rows, items)
1384  items.freeze()
1385}
1386",
1387        );
1388        assert_eq!(
1389            beside.labels[0].message,
1390            "`items` escapes into a call that writes through a `var` argument here"
1391        );
1392    }
1393
1394    #[test]
1395    fn a_call_escapes_only_when_the_callee_could_keep_the_handle() {
1396        proves(
1397            "\
1398fn total(of: Vector<Int>) -> Int {
1399  of.length()
1400}
1401
1402fn build() -> Array<Int> {
1403  var items = Vector.of(1, 2)
1404  total(items)
1405  items.freeze()
1406}
1407",
1408        );
1409        let answered = refuses(
1410            "\
1411fn wrap(one: Vector<Int>) -> Vector<Vector<Int>> {
1412  Vector.of(one)
1413}
1414
1415fn build() -> Array<Int> {
1416  var items = Vector.of(1, 2)
1417  wrap(items)
1418  items.freeze()
1419}
1420",
1421        );
1422        assert_eq!(
1423            answered.labels[0].message,
1424            "`items` escapes into a call that may answer with it here"
1425        );
1426        let written = refuses(
1427            "\
1428fn fill(var into: Vector<Int>, from: Vector<Int>) {
1429  into.push(from.length())
1430}
1431
1432fn build() -> Array<Int> {
1433  var items = Vector.of(1, 2)
1434  var sink = Vector.of(0)
1435  fill(var sink, items)
1436  items.freeze()
1437}
1438",
1439        );
1440        assert_eq!(
1441            written.labels[0].message,
1442            "`items` escapes into a call that writes through a `var` argument here"
1443        );
1444        let stored = refuses(
1445            "\
1446struct Sink {
1447  rows: Vector<Vector<Int>>
1448}
1449
1450fn keep(one: Vector<Int>, into: Sink) {
1451  var rows = into.rows
1452  rows.push(one)
1453}
1454
1455fn build(sink: Sink) -> Array<Int> {
1456  var items = Vector.of(1, 2)
1457  keep(items, sink)
1458  items.freeze()
1459}
1460",
1461        );
1462        assert_eq!(
1463            stored.labels[0].message,
1464            "`items` escapes into a call that may store it in another argument here"
1465        );
1466    }
1467
1468    /// A closure that mentions the vector holds it for as long as the closure
1469    /// lives, which this pass cannot bound.
1470    #[test]
1471    fn a_closure_capture_defeats_the_proof() {
1472        let error = refuses(
1473            "\
1474fn build() -> Array<Int> {
1475  var items = Vector.of(1, 2)
1476  let count = fn() {
1477    items.length()
1478  }
1479  count()
1480  items.freeze()
1481}
1482",
1483        );
1484        assert_eq!(
1485            error.labels[0].message,
1486            "`items` is captured by a closure here"
1487        );
1488    }
1489
1490    /// `freeze()` consumes, so a read afterwards is an error of its own,
1491    /// pointing at both ends.
1492    #[test]
1493    fn a_read_after_the_freeze_is_reported_where_it_is_written() {
1494        let error = refuses(
1495            "\
1496fn build() -> Int {
1497  var items = Vector.of(1, 2)
1498  let frozen = items.freeze()
1499  frozen.length() + items.length()
1500}
1501",
1502        );
1503        assert_eq!(error.code, USED_AFTER_FREEZE);
1504        assert_eq!(
1505            error.message,
1506            "`items` is read after its storage was consumed"
1507        );
1508        assert_eq!(error.labels[0].message, "`freeze()` took the storage here");
1509    }
1510
1511    /// A second turn would find the storage gone.
1512    #[test]
1513    fn a_freeze_a_loop_runs_twice_is_refused() {
1514        let error = refuses(
1515            "\
1516fn build(rounds: Int) -> Int {
1517  var items = Vector.of(1, 2)
1518  var total = 0
1519  for _n in 0..rounds {
1520    total += items.freeze().length()
1521  }
1522  total
1523}
1524",
1525        );
1526        assert_eq!(error.code, NOT_UNIQUE);
1527        assert!(
1528            error.labels[0].message.contains("more than once"),
1529            "{}",
1530            error.labels[0].message
1531        );
1532    }
1533
1534    /// Storage that came from somewhere this body cannot see the creation of.
1535    #[test]
1536    fn a_binding_this_body_did_not_create_is_refused() {
1537        let error = refuses(
1538            "\
1539fn fresh() -> Vector<Int> {
1540  Vector.of(1)
1541}
1542
1543fn build() -> Array<Int> {
1544  var items = fresh()
1545  items.freeze()
1546}
1547",
1548        );
1549        assert_eq!(error.code, NOT_UNIQUE);
1550        assert!(
1551            error.labels[0]
1552                .message
1553                .contains("initialised from a value this function did not create"),
1554            "{}",
1555            error.labels[0].message
1556        );
1557    }
1558
1559    /// A parameter belongs to the caller, and only a `var self` receiver can
1560    /// carry the obligation back to one.
1561    #[test]
1562    fn an_ordinary_parameter_cannot_be_frozen() {
1563        let error = refuses(
1564            "\
1565fn build(var items: Vector<Int>) -> Array<Int> {
1566  items.freeze()
1567}
1568",
1569        );
1570        assert_eq!(error.code, NOT_UNIQUE);
1571        assert!(
1572            error.labels[0]
1573                .message
1574                .contains("comes from this function's caller"),
1575            "{}",
1576            error.labels[0].message
1577        );
1578    }
1579
1580    /// The builder shape: `finish` demands a unique receiver, and the call
1581    /// site is where that is proved.
1582    #[test]
1583    fn a_var_self_method_moves_the_obligation_to_its_callers() {
1584        proves(
1585            "\
1586struct Draft {
1587  guests: Vector<String>
1588}
1589
1590impl Draft {
1591  fn add(var self, name: String) {
1592    self.guests.push(name)
1593  }
1594
1595  fn finish(var self) -> Array<String> {
1596    self.guests.freeze()
1597  }
1598}
1599
1600fn build(name: String) -> Array<String> {
1601  var fresh = Draft(guests: Vector.of())
1602  fresh.add(name)
1603  fresh.finish()
1604}
1605",
1606        );
1607    }
1608
1609    /// The same method, called on a draft a second binding also observes.
1610    #[test]
1611    fn the_demand_is_discharged_at_the_call_site_and_can_fail_there() {
1612        let error = refuses(
1613            "\
1614struct Draft {
1615  guests: Vector<String>
1616}
1617
1618impl Draft {
1619  fn finish(var self) -> Array<String> {
1620    self.guests.freeze()
1621  }
1622}
1623
1624fn build() -> Array<String> {
1625  var original = Draft(guests: Vector.of(\"a\"))
1626  var alias = original
1627  alias.guests.push(\"b\")
1628  original.finish()
1629}
1630",
1631        );
1632        assert_eq!(error.code, NOT_UNIQUE);
1633        assert_eq!(
1634            error.message,
1635            "`Draft.finish()` consumes `original.guests`, and this call cannot prove that it \
1636             holds the only handle to its storage"
1637        );
1638        assert_eq!(
1639            error.labels[0].message,
1640            "`original` is copied into another binding here"
1641        );
1642    }
1643
1644    /// Two arms are two bindings, however alike their names are.
1645    #[test]
1646    fn a_name_bound_in_two_arms_is_two_bindings() {
1647        proves(
1648            "\
1649enum Shape {
1650  Left
1651  Right
1652}
1653
1654fn render(shape: Shape) -> Int {
1655  match shape {
1656    Shape.Left => {
1657      var parts = Vector.of(1)
1658      parts.freeze().length()
1659    }
1660    Shape.Right => {
1661      var parts = Vector.of(2, 3)
1662      parts.freeze().length()
1663    }
1664  }
1665}
1666",
1667        );
1668    }
1669
1670    /// A `return` carries the site out of the function, so what is written
1671    /// after it is not read after it.
1672    #[test]
1673    fn a_freeze_a_return_carries_out_has_nothing_after_it() {
1674        proves(
1675            "\
1676fn build(early: Bool) -> Array<Int> {
1677  var items = Vector.of()
1678  if early {
1679    return items.freeze()
1680  }
1681  items.push(1)
1682  items.freeze()
1683}
1684",
1685        );
1686    }
1687
1688    // -- issue #270: freshness is a schema fact, not a name this pass reads -
1689
1690    /// `Vector.snapshot()` is `cove-schema`'s third `fresh` entry, and the
1691    /// one that needs a receiver's *settled type* rather than a bare `Ident`
1692    /// to resolve: unlike `Vector.of(...)`, `items.snapshot()` is reached
1693    /// through a value, and [`creates`] has to read that value's type off
1694    /// [`Facts::ty`] and turn it into the right builtin schema before it can
1695    /// ask whether `snapshot` is `fresh` there. Binding the result first,
1696    /// rather than freezing it as a temporary the way
1697    /// [`a_temporary_receiver_needs_no_proof`] does, is what exercises
1698    /// [`establishes`] rather than only the direct case.
1699    #[test]
1700    fn a_vectors_own_snapshot_is_a_fresh_primitive_result() {
1701        proves(
1702            "\
1703fn build(items: Vector<Int>) -> Array<Int> {
1704  var copy = items.snapshot()
1705  copy.freeze()
1706}
1707",
1708        );
1709    }
1710
1711    /// The regression this issue is named for: before, `creates()` matched
1712    /// the method name `toVector` against *any* receiver, so a user's own
1713    /// method of that name — on a type `cove-schema` says nothing about —
1714    /// was read as fresh too. `Holder.toVector()` here hands back a field
1715    /// it did not just allocate, and a caller that binds and freezes it has
1716    /// exactly the alias the proof exists to catch: `holder` and `copy`
1717    /// would share `holder.items`'s storage. A non-fresh result has to stay
1718    /// refused now that the check is `cove-schema`-driven, the same as it
1719    /// was refused by luck before — this program is why "by luck" was not
1720    /// good enough.
1721    #[test]
1722    fn a_declared_methods_result_is_not_fresh_even_when_it_shares_a_builtins_name() {
1723        let error = refuses(
1724            "\
1725struct Holder {
1726  items: Vector<Int>
1727}
1728
1729impl Holder {
1730  fn toVector(self) -> Vector<Int> {
1731    self.items
1732  }
1733}
1734
1735fn build(holder: Holder) -> Array<Int> {
1736  var copy = holder.toVector()
1737  copy.freeze()
1738}
1739",
1740        );
1741        assert_eq!(error.code, NOT_UNIQUE);
1742        assert_eq!(
1743            error.message,
1744            "`freeze()` cannot prove that `copy` holds the only handle to its storage"
1745        );
1746    }
1747
1748    /// The wrapper case the issue asks to think hardest about, in the shape
1749    /// `std.vector.filter` and `std.array.filter` are actually written:
1750    /// `var out = Vector.of(); ...; out.freeze()`. That `freeze()` is
1751    /// proved the ordinary local way, from the `Vector.of()` a few lines
1752    /// above it in the very same body — nothing here has to know `make` is
1753    /// a "wrapper" for its own proof to go through.
1754    ///
1755    /// What does not follow is that `make`'s *result* is fresh to whoever
1756    /// calls it. `establishes()` only ever asks [`creates`] about a call's
1757    /// own callee, and a call to `make` names a declared function, not a
1758    /// builtin schema entry — the same refusal
1759    /// [`a_binding_this_body_did_not_create_is_refused`] already pins for a
1760    /// single-expression wrapper. This test is the multi-statement shape a
1761    /// real one is written in, proved and refused in the same place so the
1762    /// two facts read together: a wrapper's own `freeze()` inside it is
1763    /// unaffected by this pass, and a wrapper's `return` still carries
1764    /// nothing past its own body.
1765    #[test]
1766    fn a_cove_wrappers_return_is_not_fresh_for_its_caller() {
1767        proves(
1768            "\
1769fn make() -> Array<Int> {
1770  var out = Vector.of(1, 2)
1771  out.freeze()
1772}
1773",
1774        );
1775        let error = refuses(
1776            "\
1777fn make() -> Vector<Int> {
1778  Vector.of(1, 2)
1779}
1780
1781fn build() -> Array<Int> {
1782  var log = make()
1783  log.freeze()
1784}
1785",
1786        );
1787        assert_eq!(error.code, NOT_UNIQUE);
1788        assert_eq!(
1789            error.message,
1790            "`freeze()` cannot prove that `log` holds the only handle to its storage"
1791        );
1792        assert_eq!(
1793            error.labels[0].message,
1794            "`log` is initialised from a value this function did not create, so its storage \
1795             may already have another handle"
1796        );
1797    }
1798
1799    /// The same wrapper, renamed. `creates()` never reads a call's callee
1800    /// name at all once the callee is not a builtin's own `Field` — it asks
1801    /// whether the call *resolves* to a schema entry, and a declared
1802    /// function does not, whatever it is spelled. Naming it `toVector` here
1803    /// — a name `cove-schema` itself marks `fresh` on a different type —
1804    /// is deliberate: if this pass still matched by name anywhere in this
1805    /// path, this is the program that would prove when it must not.
1806    #[test]
1807    fn renaming_the_wrapper_changes_nothing() {
1808        let error = refuses(
1809            "\
1810fn toVector() -> Vector<Int> {
1811  Vector.of(1, 2)
1812}
1813
1814fn build() -> Array<Int> {
1815  var log = toVector()
1816  log.freeze()
1817}
1818",
1819        );
1820        assert_eq!(error.code, NOT_UNIQUE);
1821        assert_eq!(
1822            error.labels[0].message,
1823            "`log` is initialised from a value this function did not create, so its storage \
1824             may already have another handle"
1825        );
1826    }
1827}