Simplify provider types to require freezing at the compiler level.

Bug: 528225104
Change-Id: I833fe5911ebda3e7de85a27fd55dd0b56a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24280
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock
index 7095a02..8d80e3f 100644
--- a/src/gn/starlark/Cargo.lock
+++ b/src/gn/starlark/Cargo.lock
@@ -24,7 +24,7 @@
 [[package]]
 name = "allocative"
 version = "0.3.6"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "allocative_derive",
  "bumpalo",
@@ -37,7 +37,7 @@
 [[package]]
 name = "allocative_derive"
 version = "0.3.6"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -67,9 +67,9 @@
 
 [[package]]
 name = "anyhow"
-version = "1.0.103"
+version = "1.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
 
 [[package]]
 name = "args"
@@ -99,13 +99,13 @@
 
 [[package]]
 name = "async-trait"
-version = "0.1.89"
+version = "0.1.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.3",
 ]
 
 [[package]]
@@ -207,9 +207,9 @@
 
 [[package]]
 name = "bytemuck"
-version = "1.25.1"
+version = "1.25.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
 dependencies = [
  "bytemuck_derive",
 ]
@@ -233,9 +233,9 @@
 
 [[package]]
 name = "cc"
-version = "1.2.67"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
+checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
 dependencies = [
  "find-msvc-tools",
  "shlex",
@@ -255,9 +255,9 @@
 
 [[package]]
 name = "clap"
-version = "4.6.2"
+version = "4.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
+checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
 dependencies = [
  "clap_builder",
 ]
@@ -291,7 +291,7 @@
 [[package]]
 name = "cmp_any"
 version = "0.8.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 
 [[package]]
 name = "cobs"
@@ -386,9 +386,9 @@
 
 [[package]]
 name = "ctor"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a394189d59f9befacce833f337f7b1eca5e9a91221bcdd4d28e0114d96e597b3"
+checksum = "e2e30e509674ef0ec91e21a7735766db37d163d46151b6a361d8b83dd79116bd"
 dependencies = [
  "link-section",
  "linktime-proc-macro",
@@ -535,7 +535,7 @@
 [[package]]
 name = "display_container"
 version = "0.9.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "either",
  "indenter",
@@ -544,7 +544,7 @@
 [[package]]
 name = "dupe"
 version = "0.9.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "dupe_derive",
 ]
@@ -552,7 +552,7 @@
 [[package]]
 name = "dupe_derive"
 version = "0.9.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -851,9 +851,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.186"
+version = "0.2.189"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
 
 [[package]]
 name = "link-cplusplus"
@@ -866,9 +866,9 @@
 
 [[package]]
 name = "link-section"
-version = "0.19.0"
+version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e333fe507b738576d6da5bb3f1a7d7a1c80307ed9ef31624c057d844c19c93e9"
+checksum = "8dc98458dfe90986c5e2f6ddcf68360c7e5c4252600153e06aa4ee8176c0f8d1"
 
 [[package]]
 name = "linktime-proc-macro"
@@ -1038,7 +1038,7 @@
 [[package]]
 name = "pagable"
 version = "0.4.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "allocative",
  "anyhow",
@@ -1074,7 +1074,7 @@
 [[package]]
 name = "pagable_derive"
 version = "0.4.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1126,9 +1126,9 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.106"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
 dependencies = [
  "unicode-ident",
 ]
@@ -1159,9 +1159,9 @@
 
 [[package]]
 name = "quote"
-version = "1.0.46"
+version = "1.0.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
 dependencies = [
  "proc-macro2",
 ]
@@ -1219,22 +1219,22 @@
 
 [[package]]
 name = "ref-cast"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d"
 dependencies = [
  "ref-cast-impl",
 ]
 
 [[package]]
 name = "ref-cast-impl"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.3",
 ]
 
 [[package]]
@@ -1384,9 +1384,9 @@
 
 [[package]]
 name = "serde"
-version = "1.0.228"
+version = "1.0.229"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
 dependencies = [
  "serde_core",
  "serde_derive",
@@ -1394,29 +1394,29 @@
 
 [[package]]
 name = "serde_core"
-version = "1.0.228"
+version = "1.0.229"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.228"
+version = "1.0.229"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.3",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.150"
+version = "1.0.151"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
 dependencies = [
  "itoa",
  "memchr",
@@ -1427,13 +1427,13 @@
 
 [[package]]
 name = "serde_repr"
-version = "0.1.20"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.3",
 ]
 
 [[package]]
@@ -1476,7 +1476,7 @@
 [[package]]
 name = "starlark"
 version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "allocative",
  "anyhow",
@@ -1519,7 +1519,7 @@
 [[package]]
 name = "starlark_derive"
 version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "dupe",
  "proc-macro2",
@@ -1530,7 +1530,7 @@
 [[package]]
 name = "starlark_map"
 version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "allocative",
  "dupe",
@@ -1545,7 +1545,7 @@
 [[package]]
 name = "starlark_syntax"
 version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "allocative",
  "annotate-snippets",
@@ -1583,7 +1583,7 @@
 [[package]]
 name = "strong_hash"
 version = "0.1.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "ref-cast",
  "strong_hash_derive",
@@ -1592,7 +1592,7 @@
 [[package]]
 name = "strong_hash_derive"
 version = "0.1.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#426a43164741eec393f074c1670c985491561ff7"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
 dependencies = [
  "quote",
  "syn 2.0.119",
@@ -1655,6 +1655,17 @@
 ]
 
 [[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
 name = "take_mut"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1691,22 +1702,22 @@
 
 [[package]]
 name = "thiserror"
-version = "2.0.18"
+version = "2.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.18"
+version = "2.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.119",
+ "syn 3.0.3",
 ]
 
 [[package]]
diff --git a/src/gn/starlark/crates/providers/src/errors.rs b/src/gn/starlark/crates/providers/src/errors.rs
index 4f5cf04..0de8529 100644
--- a/src/gn/starlark/crates/providers/src/errors.rs
+++ b/src/gn/starlark/crates/providers/src/errors.rs
@@ -19,6 +19,12 @@
     ProviderNotFrozen,
 }
 
+impl From<Error> for starlark::values::FreezeError {
+    fn from(err: Error) -> Self {
+        Self::new(err.to_string())
+    }
+}
+
 impl From<Error> for starlark::Error {
     fn from(err: Error) -> Self {
         Self::new_other(err)
diff --git a/src/gn/starlark/crates/providers/src/provider_instance.rs b/src/gn/starlark/crates/providers/src/provider_instance.rs
index 54e8284..6ca3192 100644
--- a/src/gn/starlark/crates/providers/src/provider_instance.rs
+++ b/src/gn/starlark/crates/providers/src/provider_instance.rs
@@ -11,7 +11,8 @@
     collections::{Hashed, StarlarkHasher},
     starlark_complex_value,
     values::{
-        Freeze, FrozenValueTyped, Heap, StarlarkValue, Trace, Value, ValueLifetimeless, ValueLike,
+        Freeze, FreezeResult, Freezer, Heap, StarlarkValue, Trace, Value, ValueLifetimeless,
+        ValueLike,
     },
 };
 use starlark_derive::{starlark_value, NoSerialize};
@@ -19,22 +20,39 @@
 use crate::provider_type::FrozenProviderType;
 
 /// Represents an instance of a provider.
-#[derive(Clone, Trace, Coerce, Freeze, ProvidesStaticType, Allocative, NoSerialize)]
+#[derive(Clone, Trace, Coerce, ProvidesStaticType, Allocative, NoSerialize)]
 #[repr(C)]
 pub struct ProviderInstanceGen<V: ValueLifetimeless> {
-    pub(crate) provider_type: FrozenValueTyped<'static, FrozenProviderType>,
+    pub(crate) provider_type: &'static FrozenProviderType,
     pub(crate) values: Box<[Option<V>]>,
 }
 
+impl<V: ValueLifetimeless + Freeze> Freeze for ProviderInstanceGen<V>
+where
+    V::Frozen: ValueLifetimeless,
+{
+    type Frozen = ProviderInstanceGen<V::Frozen>;
+
+    fn freeze(self, freezer: &Freezer) -> FreezeResult<Self::Frozen> {
+        Ok(ProviderInstanceGen {
+            provider_type: self.provider_type,
+            values: self
+                .values
+                .into_vec()
+                .into_iter()
+                .map(|v| v.freeze(freezer))
+                .collect::<Result<Vec<_>, _>>()?
+                .into_boxed_slice(),
+        })
+    }
+}
+
 starlark_complex_value!(pub ProviderInstance);
 
 impl<'v, V: ValueLike<'v>> ProviderInstanceGen<V>
 where
     Self: ProvidesStaticType<'v>,
 {
-    pub(crate) fn ty(&self) -> &'v FrozenProviderType {
-        self.provider_type.as_ref()
-    }
 
     pub(crate) fn ty_name(&self) -> &'static str {
         self.get_type_value_dyn().as_str()
@@ -44,7 +62,7 @@
     where
         'v: 'a,
     {
-        let fields = &self.ty().fields;
+        let fields = &self.provider_type.fields;
         fields
             .iter()
             .filter_map(move |(name, &idx)| self.values[idx].map(|val| (name.as_str(), val)))
@@ -93,15 +111,14 @@
     type Canonical = FrozenProviderInstance;
 
     fn get_type_value_dyn(&self) -> starlark::values::FrozenStringValue {
-        // Safety: ProviderInstance is only constructed when the provider is exported.
-        unsafe { self.ty().data.as_ref().unwrap_unchecked().name }
+        self.provider_type.data.name
     }
 
     fn equals(&self, other: Value<'v>) -> starlark::Result<bool> {
         let Some(other) = ProviderInstance::from_value(other) else {
             return Ok(false);
         };
-        if self.ty().id != other.ty().id {
+        if self.provider_type.id != other.provider_type.id {
             return Ok(false);
         }
         for (v1, v2) in self.values.iter().zip(other.values.iter()) {
@@ -132,7 +149,7 @@
     }
 
     fn get_attr_hashed(&self, attribute: Hashed<&str>, _heap: Heap<'v>) -> Option<Value<'v>> {
-        let &i = self.ty().fields.get_hashed(attribute)?;
+        let &i = self.provider_type.fields.get_hashed(attribute)?;
         self.values[i].map(|v| v.to_value())
     }
 
@@ -149,7 +166,7 @@
     }
 
     fn dir_attr(&self) -> Vec<String> {
-        let fields = &self.ty().fields;
+        let fields = &self.provider_type.fields;
         fields
             .iter()
             .filter_map(|(name, &idx)| {
diff --git a/src/gn/starlark/crates/providers/src/provider_type.rs b/src/gn/starlark/crates/providers/src/provider_type.rs
index 24c7569..499afa5 100644
--- a/src/gn/starlark/crates/providers/src/provider_type.rs
+++ b/src/gn/starlark/crates/providers/src/provider_type.rs
@@ -51,7 +51,7 @@
     /// The unique type identifier.
     pub(crate) id: TypeInstanceId,
     /// The configured provider fields.
-    pub(crate) data: Option<ProviderTypeData>,
+    pub(crate) data: ProviderTypeData,
     /// A mapping from field name to index.
     /// This is akin to python's `__slots__`.
     pub(crate) fields: SmallMap<String, usize>,
@@ -122,17 +122,17 @@
     ) -> starlark::Result<Value<'v>> {
         // Safety: `me` is the receiver of type `FrozenProviderType`, which is
         // guaranteed to be frozen.
-        let provider_type =
+        let provider_type: FrozenValueTyped<'static, Self> =
             unsafe { FrozenValueTyped::new_unchecked(me.unpack_frozen().unwrap_unchecked()) };
 
-        let data = self.data.as_ref().ok_or(Error::ProviderNotExported)?;
+        let data = &self.data;
         data.parameter_spec
             .parser(args, eval, |param_parser, eval| {
                 let values: Box<[Option<Value<'v>>]> = (0..self.fields.len())
                     .map(|_| param_parser.next_opt::<Value<'v>>())
                     .collect::<starlark::Result<_>>()?;
                 Ok(eval.heap().alloc_complex(ProviderInstance {
-                    provider_type,
+                    provider_type: provider_type.as_ref(),
                     values,
                 }))
             })
@@ -161,9 +161,13 @@
     type Frozen = FrozenProviderType;
 
     fn freeze(self, _freezer: &Freezer) -> FreezeResult<Self::Frozen> {
+        let data = self
+            .data
+            .into_inner()
+            .ok_or(Error::ProviderNotExported)?;
         Ok(FrozenProviderType {
             id: self.id,
-            data: self.data.into_inner(),
+            data,
             fields: self.fields,
         })
     }
@@ -235,13 +239,8 @@
     #[test]
     fn test_unexported_provider_fails_to_call() {
         let mut a = new_assert();
-        let unexported = a.pass("x = [provider(fields=['a'])]; x");
-        a.modify_globals(move |builder| {
-            builder.set("x", unexported.clone());
-        });
-
-        a.fail(
-            "MyInfo = x[0]; MyInfo()",
+        a.fail_to_freeze(
+            "x = [provider(fields=['a'])]; x",
             "The result of provider() must be assigned to a variable",
         );
     }
diff --git a/src/gn/starlark/crates/testutils/src/assert.rs b/src/gn/starlark/crates/testutils/src/assert.rs
index cba6904..d10726a 100644
--- a/src/gn/starlark/crates/testutils/src/assert.rs
+++ b/src/gn/starlark/crates/testutils/src/assert.rs
@@ -110,6 +110,13 @@
         self.assert.pass(code)
     }
 
+    /// Asserts that freezing the evaluated module fails with the expected
+    /// error.
+    #[track_caller]
+    pub fn fail_to_freeze(&mut self, code: &str, expected_error: &str) -> starlark::Error {
+        self.assert.fail_to_freeze(code, expected_error)
+    }
+
     /// Asserts that the code fails to evaluate with the expected error.
     #[track_caller]
     pub fn fail(&mut self, code: &str, expected_error: &str) -> starlark::Error {