{"record":{"id":"c7f8ae2ab2318068","repo":"gleam-lang/gleam","slug":"inmemoryfilesystem-into-files-called-on-a-clone","errorCode":null,"errorMessage":"InMemoryFileSystem::into_files called on a clone","messagePattern":"InMemoryFileSystem::into_files called on a clone","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler-core/src/io/memory.rs","lineNumber":63,"sourceCode":"\nimpl InMemoryFileSystem {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn reset(&self) {\n        self.files.deref().borrow_mut().clear();\n    }\n\n    /// Returns the contents of each file, excluding directories.\n    ///\n    /// # Panics\n    ///\n    /// Panics if this is not the only reference to the underlying files.\n    ///\n    pub fn into_contents(self) -> HashMap<Utf8PathBuf, Content> {\n        Rc::try_unwrap(self.files)\n            .expect(\"InMemoryFileSystem::into_files called on a clone\")\n            .into_inner()\n            .into_iter()\n            .filter_map(|(path, file)| file.into_content().map(|content| (path, content)))\n            .collect()\n    }\n\n    /// All files currently in the filesystem (directories are not included).\n    pub fn files(&self) -> Vec<Utf8PathBuf> {\n        self.files\n            .borrow()\n            .iter()\n            .filter(|(_, f)| !f.is_directory())\n            .map(|(path, _)| path)\n            .cloned()\n            .collect()\n    }\n\n    #[cfg(test)]","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/gleam-lang/gleam/blob/7e623aa83da3776faee50ca4ab9a6c40124acd95/compiler-core/src/io/memory.rs#L45-L81","documentation":"InMemoryFileSystem keeps its files in Rc<RefCell<HashMap<...>>>; into_contents consumes self and calls Rc::try_unwrap(...).expect(\"InMemoryFileSystem::into_files called on a clone\") to take sole ownership (memory.rs:63). Because cloning the filesystem is a cheap Rc clone, any clone still alive anywhere (a test variable, a struct field, a dropped-later scope) makes try_unwrap fail and this panic fire. The # Panics doc on the function states exactly this contract.","triggerScenarios":"Any code that does `let fs2 = fs.clone()` (tests, wrappers, the test-package-compiler/test-project-compiler harnesses) and then calls fs.into_contents() while fs2 is still in scope or moved into another object. Also a clone captured by a closure or held in a Reader/Writer wrapper.","commonSituations":"Writing unit tests against gleam_core with the in-memory IO: you clone the filesystem to hand to the compiler and keep another copy for assertions, then call into_contents for golden-file comparison (as native_file_copier/tests.rs does dozens of times).","solutions":["Drop every clone before converting: `drop(fs_clone);` or structure the test so clones go out of scope before `fs.into_contents()`.","Don't clone at all — use the non-consuming `fs.files()` / `fs.read_bytes()` for inspection and save into_contents for the final ownership handoff.","If a wrapper type holds a clone, redesign it to borrow (&InMemoryFileSystem) instead of owning a clone.","For maintainers: consider returning Result or exposing a strong_count check instead of panicking."],"exampleFix":"// before: clone alive during conversion\nlet fs = InMemoryFileSystem::new();\nlet snapshot = fs.clone();\n// ...compile with fs...\nlet files = fs.into_contents(); // panics: Rc::try_unwrap fails\n\n// after: drop the clone first, or don't clone\nlet fs = InMemoryFileSystem::new();\n// ...compile with fs...\nlet names = fs.files();      // inspect without consuming\ndrop(snapshot);              // if you must clone, drop it first\nlet files = fs.into_contents();","handlingStrategy":"validation","validationCode":"// Enforce the single-owner contract before converting (library authors with\n// access to the internals):\nfn into_contents_if_sole(fs: InMemoryFileSystem) -> Result<HashMap<Utf8PathBuf, Content>, InMemoryFileSystem> {\n    // cheap structural check: clone discipline is on you; track clones manually\n    // e.g. wrap InMemoryFileSystem and count clones handed out, require count == 0 here\n}\n\n// Practical caller-side validation: audit that no clone exists\ndrop(snapshot_writer); // every variable/field holding fs.clone()\nlet files = fs.into_contents();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never keep an InMemoryFileSystem clone alive across into_contents(); scope clones so they drop first.","Prefer the non-consuming API (files(), read_bytes()) for assertions; reserve into_contents for the final handoff.","In tests, hand ownership of the fs to the compiler and inspect results via the returned files map instead of cloning for peeking."],"tags":["panic","rc","try-unwrap","in-memory-fs","tests","ownership","gleam-core"],"backgroundTag":"rc-try-unwrap-panic","analyzedSha":"7e623aa83da3776faee50ca4ab9a6c40124acd95","analyzedAt":"2026-08-17T00:07:02.091Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}