Skip to main content

tensorlogic_adapters/
linear.rs

1//! Linear types for resource tracking and single-use guarantees.
2//!
3//! Linear types ensure that resources are used exactly once, preventing issues like
4//! double-free, use-after-free, and resource leaks. This is particularly useful for
5//! managing GPU memory, file handles, and other exclusive resources in tensor computations.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use tensorlogic_adapters::{LinearType, LinearKind, LinearContext, Ownership};
11//!
12//! // Create a linear tensor type (must be used exactly once)
13//! let linear_tensor = LinearType::new("Tensor")
14//!     .with_kind(LinearKind::Linear)
15//!     .with_name("LinearTensor");
16//!
17//! // Create an affine type (can be used at most once)
18//! let affine_tensor = LinearType::new("Tensor")
19//!     .with_kind(LinearKind::Affine);
20//!
21//! // Create a relevant type (must be used at least once)
22//! let relevant_tensor = LinearType::new("Tensor")
23//!     .with_kind(LinearKind::Relevant);
24//! ```
25
26use std::collections::{HashMap, HashSet};
27use std::fmt;
28
29/// The kind of linearity constraint.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum LinearKind {
32    /// Unrestricted: can be used any number of times (standard types)
33    Unrestricted,
34    /// Linear: must be used exactly once
35    Linear,
36    /// Affine: can be used at most once (can be dropped)
37    Affine,
38    /// Relevant: must be used at least once (can be copied)
39    Relevant,
40}
41
42impl LinearKind {
43    /// Check if this kind allows copying.
44    pub fn allows_copy(&self) -> bool {
45        matches!(self, LinearKind::Unrestricted | LinearKind::Relevant)
46    }
47
48    /// Check if this kind allows dropping without use.
49    pub fn allows_drop(&self) -> bool {
50        matches!(self, LinearKind::Unrestricted | LinearKind::Affine)
51    }
52
53    /// Check if this kind requires at least one use.
54    pub fn requires_use(&self) -> bool {
55        matches!(self, LinearKind::Linear | LinearKind::Relevant)
56    }
57
58    /// Check if this kind limits to at most one use.
59    pub fn limits_use(&self) -> bool {
60        matches!(self, LinearKind::Linear | LinearKind::Affine)
61    }
62
63    /// Get the join (least upper bound) of two kinds.
64    pub fn join(&self, other: &LinearKind) -> LinearKind {
65        match (*self, *other) {
66            (LinearKind::Unrestricted, _) | (_, LinearKind::Unrestricted) => {
67                LinearKind::Unrestricted
68            }
69            (LinearKind::Linear, LinearKind::Linear) => LinearKind::Linear,
70            (LinearKind::Affine, LinearKind::Affine) => LinearKind::Affine,
71            (LinearKind::Relevant, LinearKind::Relevant) => LinearKind::Relevant,
72            (LinearKind::Linear, LinearKind::Affine) | (LinearKind::Affine, LinearKind::Linear) => {
73                LinearKind::Affine
74            }
75            (LinearKind::Linear, LinearKind::Relevant)
76            | (LinearKind::Relevant, LinearKind::Linear) => LinearKind::Relevant,
77            (LinearKind::Affine, LinearKind::Relevant)
78            | (LinearKind::Relevant, LinearKind::Affine) => LinearKind::Unrestricted,
79        }
80    }
81
82    /// Get the meet (greatest lower bound) of two kinds.
83    pub fn meet(&self, other: &LinearKind) -> LinearKind {
84        match (*self, *other) {
85            (LinearKind::Linear, _) | (_, LinearKind::Linear) => LinearKind::Linear,
86            (LinearKind::Affine, LinearKind::Affine) => LinearKind::Affine,
87            (LinearKind::Relevant, LinearKind::Relevant) => LinearKind::Relevant,
88            (LinearKind::Affine, LinearKind::Relevant)
89            | (LinearKind::Relevant, LinearKind::Affine) => LinearKind::Linear,
90            (LinearKind::Unrestricted, other) | (other, LinearKind::Unrestricted) => other,
91        }
92    }
93}
94
95impl fmt::Display for LinearKind {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            LinearKind::Unrestricted => write!(f, "unrestricted"),
99            LinearKind::Linear => write!(f, "linear"),
100            LinearKind::Affine => write!(f, "affine"),
101            LinearKind::Relevant => write!(f, "relevant"),
102        }
103    }
104}
105
106/// Ownership state of a resource.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum Ownership {
109    /// Resource is owned and can be used
110    Owned,
111    /// Resource has been moved/consumed
112    Moved,
113    /// Resource has been borrowed (still owned but in use)
114    Borrowed,
115    /// Resource has been dropped
116    Dropped,
117}
118
119impl fmt::Display for Ownership {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Ownership::Owned => write!(f, "owned"),
123            Ownership::Moved => write!(f, "moved"),
124            Ownership::Borrowed => write!(f, "borrowed"),
125            Ownership::Dropped => write!(f, "dropped"),
126        }
127    }
128}
129
130/// A linear type with usage constraints.
131#[derive(Debug, Clone)]
132pub struct LinearType {
133    /// Base type name
134    pub base_type: String,
135    /// Linearity kind
136    pub kind: LinearKind,
137    /// Optional type name
138    pub name: Option<String>,
139    /// Description
140    pub description: Option<String>,
141    /// Resource tags (for grouping related resources)
142    pub tags: Vec<String>,
143}
144
145impl LinearType {
146    /// Create a new linear type with default unrestricted kind.
147    pub fn new(base_type: impl Into<String>) -> Self {
148        LinearType {
149            base_type: base_type.into(),
150            kind: LinearKind::Unrestricted,
151            name: None,
152            description: None,
153            tags: Vec::new(),
154        }
155    }
156
157    /// Create a linear type that must be used exactly once.
158    pub fn linear(base_type: impl Into<String>) -> Self {
159        LinearType {
160            base_type: base_type.into(),
161            kind: LinearKind::Linear,
162            name: None,
163            description: None,
164            tags: Vec::new(),
165        }
166    }
167
168    /// Create an affine type that can be used at most once.
169    pub fn affine(base_type: impl Into<String>) -> Self {
170        LinearType {
171            base_type: base_type.into(),
172            kind: LinearKind::Affine,
173            name: None,
174            description: None,
175            tags: Vec::new(),
176        }
177    }
178
179    /// Create a relevant type that must be used at least once.
180    pub fn relevant(base_type: impl Into<String>) -> Self {
181        LinearType {
182            base_type: base_type.into(),
183            kind: LinearKind::Relevant,
184            name: None,
185            description: None,
186            tags: Vec::new(),
187        }
188    }
189
190    /// Set the linearity kind.
191    pub fn with_kind(mut self, kind: LinearKind) -> Self {
192        self.kind = kind;
193        self
194    }
195
196    /// Set the type name.
197    pub fn with_name(mut self, name: impl Into<String>) -> Self {
198        self.name = Some(name.into());
199        self
200    }
201
202    /// Set the description.
203    pub fn with_description(mut self, description: impl Into<String>) -> Self {
204        self.description = Some(description.into());
205        self
206    }
207
208    /// Add a resource tag.
209    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
210        self.tags.push(tag.into());
211        self
212    }
213
214    /// Get the effective type name.
215    pub fn type_name(&self) -> &str {
216        self.name.as_deref().unwrap_or(&self.base_type)
217    }
218
219    /// Check if this type allows copying.
220    pub fn allows_copy(&self) -> bool {
221        self.kind.allows_copy()
222    }
223
224    /// Check if this type allows dropping.
225    pub fn allows_drop(&self) -> bool {
226        self.kind.allows_drop()
227    }
228}
229
230/// A resource tracked by the linear type system.
231#[derive(Debug, Clone)]
232pub struct Resource {
233    /// Resource name/identifier
234    pub name: String,
235    /// Resource type
236    pub ty: LinearType,
237    /// Current ownership state
238    pub ownership: Ownership,
239    /// Number of times the resource has been used
240    pub use_count: usize,
241    /// Location where the resource was created
242    pub created_at: Option<String>,
243    /// Location where the resource was last used
244    pub last_used_at: Option<String>,
245}
246
247impl Resource {
248    /// Create a new owned resource.
249    pub fn new(name: impl Into<String>, ty: LinearType) -> Self {
250        Resource {
251            name: name.into(),
252            ty,
253            ownership: Ownership::Owned,
254            use_count: 0,
255            created_at: None,
256            last_used_at: None,
257        }
258    }
259
260    /// Set the creation location.
261    pub fn with_created_at(mut self, location: impl Into<String>) -> Self {
262        self.created_at = Some(location.into());
263        self
264    }
265
266    /// Check if the resource can be used.
267    pub fn can_use(&self) -> bool {
268        matches!(self.ownership, Ownership::Owned)
269            || (matches!(self.ownership, Ownership::Borrowed) && self.ty.kind.allows_copy())
270    }
271
272    /// Check if the resource can be moved.
273    pub fn can_move(&self) -> bool {
274        matches!(self.ownership, Ownership::Owned)
275    }
276
277    /// Check if the resource can be dropped.
278    pub fn can_drop(&self) -> bool {
279        self.ty.allows_drop() || self.use_count > 0
280    }
281
282    /// Use the resource, returning an error if not allowed.
283    pub fn use_resource(&mut self, location: impl Into<String>) -> Result<(), LinearError> {
284        if !self.can_use() {
285            return Err(LinearError::UseAfterMove {
286                resource: self.name.clone(),
287                state: self.ownership,
288            });
289        }
290
291        if self.ty.kind.limits_use() && self.use_count > 0 {
292            return Err(LinearError::MultipleUse {
293                resource: self.name.clone(),
294                count: self.use_count + 1,
295            });
296        }
297
298        self.use_count += 1;
299        self.last_used_at = Some(location.into());
300        Ok(())
301    }
302
303    /// Move the resource to a new owner.
304    pub fn move_to(&mut self, location: impl Into<String>) -> Result<(), LinearError> {
305        if !self.can_move() {
306            return Err(LinearError::UseAfterMove {
307                resource: self.name.clone(),
308                state: self.ownership,
309            });
310        }
311
312        self.ownership = Ownership::Moved;
313        self.last_used_at = Some(location.into());
314        Ok(())
315    }
316
317    /// Drop the resource.
318    pub fn drop_resource(&mut self) -> Result<(), LinearError> {
319        if self.ty.kind.requires_use() && self.use_count == 0 {
320            return Err(LinearError::UnusedResource {
321                resource: self.name.clone(),
322                kind: self.ty.kind,
323            });
324        }
325
326        self.ownership = Ownership::Dropped;
327        Ok(())
328    }
329
330    /// Validate the resource state at the end of its scope.
331    pub fn validate_end_of_scope(&self) -> Result<(), LinearError> {
332        match self.ownership {
333            Ownership::Owned => {
334                if self.ty.kind.requires_use() && self.use_count == 0 {
335                    Err(LinearError::UnusedResource {
336                        resource: self.name.clone(),
337                        kind: self.ty.kind,
338                    })
339                } else {
340                    Ok(())
341                }
342            }
343            Ownership::Borrowed => Err(LinearError::BorrowedAtEndOfScope {
344                resource: self.name.clone(),
345            }),
346            _ => Ok(()),
347        }
348    }
349}
350
351/// Error types for linear type violations.
352#[derive(Debug, Clone)]
353pub enum LinearError {
354    /// Attempted to use a resource after it was moved
355    UseAfterMove { resource: String, state: Ownership },
356    /// Attempted to use a linear resource multiple times
357    MultipleUse { resource: String, count: usize },
358    /// A linear/relevant resource was never used
359    UnusedResource { resource: String, kind: LinearKind },
360    /// Resource was still borrowed at end of scope
361    BorrowedAtEndOfScope { resource: String },
362    /// Unknown resource
363    UnknownResource { resource: String },
364    /// Resource already exists
365    DuplicateResource { resource: String },
366}
367
368impl fmt::Display for LinearError {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        match self {
371            LinearError::UseAfterMove { resource, state } => {
372                write!(f, "Cannot use resource '{}': it is {}", resource, state)
373            }
374            LinearError::MultipleUse { resource, count } => {
375                write!(
376                    f,
377                    "Resource '{}' used {} times but must be used exactly once",
378                    resource, count
379                )
380            }
381            LinearError::UnusedResource { resource, kind } => {
382                write!(f, "{} resource '{}' was never used", kind, resource)
383            }
384            LinearError::BorrowedAtEndOfScope { resource } => {
385                write!(
386                    f,
387                    "Resource '{}' is still borrowed at end of scope",
388                    resource
389                )
390            }
391            LinearError::UnknownResource { resource } => {
392                write!(f, "Unknown resource '{}'", resource)
393            }
394            LinearError::DuplicateResource { resource } => {
395                write!(f, "Resource '{}' already exists", resource)
396            }
397        }
398    }
399}
400
401impl std::error::Error for LinearError {}
402
403/// Context for tracking linear resources.
404#[derive(Debug, Clone, Default)]
405pub struct LinearContext {
406    /// Tracked resources
407    resources: HashMap<String, Resource>,
408    /// Resource aliases (for tracking moves)
409    aliases: HashMap<String, String>,
410    /// Scope stack for nested scopes
411    scope_stack: Vec<HashSet<String>>,
412}
413
414impl LinearContext {
415    /// Create a new empty context.
416    pub fn new() -> Self {
417        LinearContext {
418            resources: HashMap::new(),
419            aliases: HashMap::new(),
420            scope_stack: vec![HashSet::new()],
421        }
422    }
423
424    /// Enter a new scope.
425    pub fn enter_scope(&mut self) {
426        self.scope_stack.push(HashSet::new());
427    }
428
429    /// Exit the current scope, validating all resources.
430    pub fn exit_scope(&mut self) -> Result<(), Vec<LinearError>> {
431        let scope = match self.scope_stack.pop() {
432            Some(s) => s,
433            None => return Ok(()),
434        };
435
436        let mut errors = Vec::new();
437
438        for resource_name in scope {
439            if let Some(resource) = self.resources.get(&resource_name) {
440                if let Err(e) = resource.validate_end_of_scope() {
441                    errors.push(e);
442                }
443            }
444        }
445
446        // Remove resources from this scope
447        for resource_name in self.scope_stack.last().cloned().unwrap_or_default() {
448            self.resources.remove(&resource_name);
449        }
450
451        if errors.is_empty() {
452            Ok(())
453        } else {
454            Err(errors)
455        }
456    }
457
458    /// Add a new resource to the current scope.
459    pub fn add_resource(&mut self, resource: Resource) -> Result<(), LinearError> {
460        if self.resources.contains_key(&resource.name) {
461            return Err(LinearError::DuplicateResource {
462                resource: resource.name,
463            });
464        }
465
466        let name = resource.name.clone();
467        self.resources.insert(name.clone(), resource);
468
469        if let Some(scope) = self.scope_stack.last_mut() {
470            scope.insert(name);
471        }
472
473        Ok(())
474    }
475
476    /// Create and add a new resource.
477    pub fn create_resource(
478        &mut self,
479        name: impl Into<String>,
480        ty: LinearType,
481        location: impl Into<String>,
482    ) -> Result<(), LinearError> {
483        let resource = Resource::new(name, ty).with_created_at(location);
484        self.add_resource(resource)
485    }
486
487    /// Get a resource by name.
488    pub fn get_resource(&self, name: &str) -> Option<&Resource> {
489        self.resolve_alias(name).and_then(|n| self.resources.get(n))
490    }
491
492    /// Get a mutable reference to a resource.
493    pub fn get_resource_mut(&mut self, name: &str) -> Option<&mut Resource> {
494        let resolved = self.resolve_alias(name).map(|s| s.to_string());
495        resolved.and_then(move |n| self.resources.get_mut(&n))
496    }
497
498    /// Resolve an alias to the actual resource name.
499    fn resolve_alias<'a>(&'a self, name: &'a str) -> Option<&'a str> {
500        if self.resources.contains_key(name) {
501            return Some(name);
502        }
503        self.aliases.get(name).map(|s| s.as_str())
504    }
505
506    /// Use a resource.
507    pub fn use_resource(
508        &mut self,
509        name: &str,
510        location: impl Into<String>,
511    ) -> Result<(), LinearError> {
512        let resource = self
513            .get_resource_mut(name)
514            .ok_or_else(|| LinearError::UnknownResource {
515                resource: name.to_string(),
516            })?;
517        resource.use_resource(location)
518    }
519
520    /// Move a resource to a new name.
521    pub fn move_resource(
522        &mut self,
523        from: &str,
524        to: impl Into<String>,
525        location: impl Into<String>,
526    ) -> Result<(), LinearError> {
527        let to = to.into();
528        let location = location.into();
529
530        // Get the source resource
531        let from_resolved = self
532            .resolve_alias(from)
533            .ok_or_else(|| LinearError::UnknownResource {
534                resource: from.to_string(),
535            })?
536            .to_string();
537
538        // Mark the source as moved
539        let resource =
540            self.resources
541                .get_mut(&from_resolved)
542                .ok_or_else(|| LinearError::UnknownResource {
543                    resource: from.to_string(),
544                })?;
545        resource.move_to(&location)?;
546
547        // Create an alias from the new name to the original
548        self.aliases.insert(to.clone(), from_resolved.clone());
549
550        // Add to current scope
551        if let Some(scope) = self.scope_stack.last_mut() {
552            scope.insert(to);
553        }
554
555        Ok(())
556    }
557
558    /// Drop a resource.
559    pub fn drop_resource(&mut self, name: &str) -> Result<(), LinearError> {
560        let resource = self
561            .get_resource_mut(name)
562            .ok_or_else(|| LinearError::UnknownResource {
563                resource: name.to_string(),
564            })?;
565        resource.drop_resource()
566    }
567
568    /// Validate all resources at the end.
569    pub fn validate_all(&self) -> Result<(), Vec<LinearError>> {
570        let mut errors = Vec::new();
571
572        for resource in self.resources.values() {
573            if let Err(e) = resource.validate_end_of_scope() {
574                errors.push(e);
575            }
576        }
577
578        if errors.is_empty() {
579            Ok(())
580        } else {
581            Err(errors)
582        }
583    }
584
585    /// Get all resource names.
586    pub fn resource_names(&self) -> Vec<&str> {
587        self.resources.keys().map(|s| s.as_str()).collect()
588    }
589
590    /// Get the number of tracked resources.
591    pub fn len(&self) -> usize {
592        self.resources.len()
593    }
594
595    /// Check if the context is empty.
596    pub fn is_empty(&self) -> bool {
597        self.resources.is_empty()
598    }
599
600    /// Get usage statistics.
601    pub fn statistics(&self) -> LinearStatistics {
602        let mut total = 0;
603        let mut used = 0;
604        let mut unused = 0;
605        let mut moved = 0;
606
607        for resource in self.resources.values() {
608            total += 1;
609            match resource.ownership {
610                Ownership::Moved => moved += 1,
611                _ => {
612                    if resource.use_count > 0 {
613                        used += 1;
614                    } else {
615                        unused += 1;
616                    }
617                }
618            }
619        }
620
621        LinearStatistics {
622            total,
623            used,
624            unused,
625            moved,
626        }
627    }
628}
629
630/// Statistics about linear resource usage.
631#[derive(Debug, Clone)]
632pub struct LinearStatistics {
633    /// Total number of resources
634    pub total: usize,
635    /// Number of used resources
636    pub used: usize,
637    /// Number of unused resources
638    pub unused: usize,
639    /// Number of moved resources
640    pub moved: usize,
641}
642
643/// Registry for linear type definitions.
644#[derive(Debug, Clone, Default)]
645pub struct LinearTypeRegistry {
646    /// Registered types
647    types: HashMap<String, LinearType>,
648}
649
650impl LinearTypeRegistry {
651    /// Create a new empty registry.
652    pub fn new() -> Self {
653        LinearTypeRegistry {
654            types: HashMap::new(),
655        }
656    }
657
658    /// Create a registry with common types.
659    pub fn with_builtins() -> Self {
660        let mut registry = LinearTypeRegistry::new();
661
662        // GPU tensor (linear - must be freed)
663        registry.register(
664            LinearType::linear("Tensor")
665                .with_name("GpuTensor")
666                .with_tag("gpu")
667                .with_description("GPU tensor that must be explicitly freed"),
668        );
669
670        // File handle (affine - can be dropped)
671        registry.register(
672            LinearType::affine("FileHandle")
673                .with_name("FileHandle")
674                .with_tag("io")
675                .with_description("File handle that can be closed or dropped"),
676        );
677
678        // Network connection (linear)
679        registry.register(
680            LinearType::linear("Connection")
681                .with_name("NetworkConnection")
682                .with_tag("network")
683                .with_description("Network connection that must be closed"),
684        );
685
686        // Mutex guard (linear)
687        registry.register(
688            LinearType::linear("Guard")
689                .with_name("MutexGuard")
690                .with_tag("sync")
691                .with_description("Mutex guard that must be released"),
692        );
693
694        registry
695    }
696
697    /// Register a linear type.
698    pub fn register(&mut self, ty: LinearType) {
699        let name = ty.type_name().to_string();
700        self.types.insert(name, ty);
701    }
702
703    /// Get a type by name.
704    pub fn get(&self, name: &str) -> Option<&LinearType> {
705        self.types.get(name)
706    }
707
708    /// Check if a type exists.
709    pub fn contains(&self, name: &str) -> bool {
710        self.types.contains_key(name)
711    }
712
713    /// Get all type names.
714    pub fn type_names(&self) -> Vec<&str> {
715        self.types.keys().map(|s| s.as_str()).collect()
716    }
717
718    /// Get the number of registered types.
719    pub fn len(&self) -> usize {
720        self.types.len()
721    }
722
723    /// Check if the registry is empty.
724    pub fn is_empty(&self) -> bool {
725        self.types.is_empty()
726    }
727}
728
729#[cfg(test)]
730mod tests {
731    use super::*;
732
733    #[test]
734    fn test_linear_kind_properties() {
735        assert!(!LinearKind::Linear.allows_copy());
736        assert!(!LinearKind::Linear.allows_drop());
737
738        assert!(!LinearKind::Affine.allows_copy());
739        assert!(LinearKind::Affine.allows_drop());
740
741        assert!(LinearKind::Relevant.allows_copy());
742        assert!(!LinearKind::Relevant.allows_drop());
743
744        assert!(LinearKind::Unrestricted.allows_copy());
745        assert!(LinearKind::Unrestricted.allows_drop());
746    }
747
748    #[test]
749    fn test_linear_kind_join() {
750        assert_eq!(
751            LinearKind::Linear.join(&LinearKind::Linear),
752            LinearKind::Linear
753        );
754        assert_eq!(
755            LinearKind::Affine.join(&LinearKind::Relevant),
756            LinearKind::Unrestricted
757        );
758    }
759
760    #[test]
761    fn test_linear_kind_meet() {
762        assert_eq!(
763            LinearKind::Affine.meet(&LinearKind::Relevant),
764            LinearKind::Linear
765        );
766        assert_eq!(
767            LinearKind::Linear.meet(&LinearKind::Affine),
768            LinearKind::Linear
769        );
770    }
771
772    #[test]
773    fn test_create_resource() {
774        let mut ctx = LinearContext::new();
775        let ty = LinearType::linear("Tensor");
776
777        ctx.create_resource("x", ty, "line 1").unwrap();
778
779        assert!(ctx.get_resource("x").is_some());
780    }
781
782    #[test]
783    fn test_use_linear_resource_once() {
784        let mut ctx = LinearContext::new();
785        let ty = LinearType::linear("Tensor");
786
787        ctx.create_resource("x", ty, "line 1").unwrap();
788        ctx.use_resource("x", "line 5").unwrap();
789
790        let resource = ctx.get_resource("x").unwrap();
791        assert_eq!(resource.use_count, 1);
792    }
793
794    #[test]
795    fn test_use_linear_resource_twice_fails() {
796        let mut ctx = LinearContext::new();
797        let ty = LinearType::linear("Tensor");
798
799        ctx.create_resource("x", ty, "line 1").unwrap();
800        ctx.use_resource("x", "line 5").unwrap();
801
802        let result = ctx.use_resource("x", "line 10");
803        assert!(matches!(result, Err(LinearError::MultipleUse { .. })));
804    }
805
806    #[test]
807    fn test_affine_can_be_dropped() {
808        let mut ctx = LinearContext::new();
809        let ty = LinearType::affine("Handle");
810
811        ctx.create_resource("h", ty, "line 1").unwrap();
812        ctx.drop_resource("h").unwrap();
813    }
814
815    #[test]
816    fn test_linear_unused_fails() {
817        let mut ctx = LinearContext::new();
818        let ty = LinearType::linear("Tensor");
819
820        ctx.create_resource("x", ty, "line 1").unwrap();
821
822        let result = ctx.validate_all();
823        assert!(result.is_err());
824    }
825
826    #[test]
827    fn test_relevant_can_be_used_multiple_times() {
828        let mut ctx = LinearContext::new();
829        let ty = LinearType::relevant("Value");
830
831        ctx.create_resource("v", ty, "line 1").unwrap();
832        ctx.use_resource("v", "line 5").unwrap();
833        ctx.use_resource("v", "line 10").unwrap();
834        ctx.use_resource("v", "line 15").unwrap();
835
836        let resource = ctx.get_resource("v").unwrap();
837        assert_eq!(resource.use_count, 3);
838    }
839
840    #[test]
841    fn test_move_resource() {
842        let mut ctx = LinearContext::new();
843        let ty = LinearType::linear("Tensor");
844
845        ctx.create_resource("x", ty, "line 1").unwrap();
846        ctx.move_resource("x", "y", "line 5").unwrap();
847
848        // x should be moved
849        let x = ctx.get_resource("x").unwrap();
850        assert_eq!(x.ownership, Ownership::Moved);
851    }
852
853    #[test]
854    fn test_use_after_move_fails() {
855        let mut ctx = LinearContext::new();
856        let ty = LinearType::linear("Tensor");
857
858        ctx.create_resource("x", ty, "line 1").unwrap();
859        ctx.move_resource("x", "y", "line 5").unwrap();
860
861        let result = ctx.use_resource("x", "line 10");
862        assert!(matches!(result, Err(LinearError::UseAfterMove { .. })));
863    }
864
865    #[test]
866    fn test_scope_tracking() {
867        let mut ctx = LinearContext::new();
868
869        ctx.enter_scope();
870
871        let ty = LinearType::linear("Tensor");
872        ctx.create_resource("x", ty, "line 1").unwrap();
873        ctx.use_resource("x", "line 5").unwrap();
874
875        ctx.exit_scope().unwrap();
876    }
877
878    #[test]
879    fn test_scope_with_unused_linear() {
880        let mut ctx = LinearContext::new();
881
882        ctx.enter_scope();
883
884        let ty = LinearType::linear("Tensor");
885        ctx.create_resource("x", ty, "line 1").unwrap();
886        // Not using x
887
888        let result = ctx.exit_scope();
889        assert!(result.is_err());
890    }
891
892    #[test]
893    fn test_statistics() {
894        let mut ctx = LinearContext::new();
895
896        ctx.create_resource("a", LinearType::linear("T"), "1")
897            .unwrap();
898        ctx.create_resource("b", LinearType::linear("T"), "2")
899            .unwrap();
900        ctx.create_resource("c", LinearType::linear("T"), "3")
901            .unwrap();
902
903        ctx.use_resource("a", "10").unwrap();
904        ctx.move_resource("b", "d", "20").unwrap();
905
906        let stats = ctx.statistics();
907        assert_eq!(stats.total, 3);
908        assert_eq!(stats.used, 1);
909        assert_eq!(stats.unused, 1);
910        assert_eq!(stats.moved, 1);
911    }
912
913    #[test]
914    fn test_registry_builtins() {
915        let registry = LinearTypeRegistry::with_builtins();
916
917        assert!(registry.contains("GpuTensor"));
918        assert!(registry.contains("FileHandle"));
919        assert!(registry.contains("NetworkConnection"));
920
921        let gpu = registry.get("GpuTensor").unwrap();
922        assert_eq!(gpu.kind, LinearKind::Linear);
923    }
924
925    #[test]
926    fn test_duplicate_resource() {
927        let mut ctx = LinearContext::new();
928        let ty = LinearType::linear("Tensor");
929
930        ctx.create_resource("x", ty.clone(), "line 1").unwrap();
931        let result = ctx.create_resource("x", ty, "line 5");
932
933        assert!(matches!(result, Err(LinearError::DuplicateResource { .. })));
934    }
935
936    #[test]
937    fn test_unknown_resource() {
938        let mut ctx = LinearContext::new();
939        let result = ctx.use_resource("unknown", "line 1");
940
941        assert!(matches!(result, Err(LinearError::UnknownResource { .. })));
942    }
943
944    #[test]
945    fn test_linear_type_with_tags() {
946        let ty = LinearType::linear("Resource")
947            .with_tag("gpu")
948            .with_tag("memory");
949
950        assert_eq!(ty.tags.len(), 2);
951        assert!(ty.tags.contains(&"gpu".to_string()));
952    }
953}