1use std::collections::{HashMap, HashSet};
27use std::fmt;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum LinearKind {
32 Unrestricted,
34 Linear,
36 Affine,
38 Relevant,
40}
41
42impl LinearKind {
43 pub fn allows_copy(&self) -> bool {
45 matches!(self, LinearKind::Unrestricted | LinearKind::Relevant)
46 }
47
48 pub fn allows_drop(&self) -> bool {
50 matches!(self, LinearKind::Unrestricted | LinearKind::Affine)
51 }
52
53 pub fn requires_use(&self) -> bool {
55 matches!(self, LinearKind::Linear | LinearKind::Relevant)
56 }
57
58 pub fn limits_use(&self) -> bool {
60 matches!(self, LinearKind::Linear | LinearKind::Affine)
61 }
62
63 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum Ownership {
109 Owned,
111 Moved,
113 Borrowed,
115 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#[derive(Debug, Clone)]
132pub struct LinearType {
133 pub base_type: String,
135 pub kind: LinearKind,
137 pub name: Option<String>,
139 pub description: Option<String>,
141 pub tags: Vec<String>,
143}
144
145impl LinearType {
146 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 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 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 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 pub fn with_kind(mut self, kind: LinearKind) -> Self {
192 self.kind = kind;
193 self
194 }
195
196 pub fn with_name(mut self, name: impl Into<String>) -> Self {
198 self.name = Some(name.into());
199 self
200 }
201
202 pub fn with_description(mut self, description: impl Into<String>) -> Self {
204 self.description = Some(description.into());
205 self
206 }
207
208 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
210 self.tags.push(tag.into());
211 self
212 }
213
214 pub fn type_name(&self) -> &str {
216 self.name.as_deref().unwrap_or(&self.base_type)
217 }
218
219 pub fn allows_copy(&self) -> bool {
221 self.kind.allows_copy()
222 }
223
224 pub fn allows_drop(&self) -> bool {
226 self.kind.allows_drop()
227 }
228}
229
230#[derive(Debug, Clone)]
232pub struct Resource {
233 pub name: String,
235 pub ty: LinearType,
237 pub ownership: Ownership,
239 pub use_count: usize,
241 pub created_at: Option<String>,
243 pub last_used_at: Option<String>,
245}
246
247impl Resource {
248 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 pub fn with_created_at(mut self, location: impl Into<String>) -> Self {
262 self.created_at = Some(location.into());
263 self
264 }
265
266 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 pub fn can_move(&self) -> bool {
274 matches!(self.ownership, Ownership::Owned)
275 }
276
277 pub fn can_drop(&self) -> bool {
279 self.ty.allows_drop() || self.use_count > 0
280 }
281
282 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 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 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 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#[derive(Debug, Clone)]
353pub enum LinearError {
354 UseAfterMove { resource: String, state: Ownership },
356 MultipleUse { resource: String, count: usize },
358 UnusedResource { resource: String, kind: LinearKind },
360 BorrowedAtEndOfScope { resource: String },
362 UnknownResource { resource: String },
364 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#[derive(Debug, Clone, Default)]
405pub struct LinearContext {
406 resources: HashMap<String, Resource>,
408 aliases: HashMap<String, String>,
410 scope_stack: Vec<HashSet<String>>,
412}
413
414impl LinearContext {
415 pub fn new() -> Self {
417 LinearContext {
418 resources: HashMap::new(),
419 aliases: HashMap::new(),
420 scope_stack: vec![HashSet::new()],
421 }
422 }
423
424 pub fn enter_scope(&mut self) {
426 self.scope_stack.push(HashSet::new());
427 }
428
429 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 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 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 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 pub fn get_resource(&self, name: &str) -> Option<&Resource> {
489 self.resolve_alias(name).and_then(|n| self.resources.get(n))
490 }
491
492 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 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 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 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 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 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 self.aliases.insert(to.clone(), from_resolved.clone());
549
550 if let Some(scope) = self.scope_stack.last_mut() {
552 scope.insert(to);
553 }
554
555 Ok(())
556 }
557
558 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 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 pub fn resource_names(&self) -> Vec<&str> {
587 self.resources.keys().map(|s| s.as_str()).collect()
588 }
589
590 pub fn len(&self) -> usize {
592 self.resources.len()
593 }
594
595 pub fn is_empty(&self) -> bool {
597 self.resources.is_empty()
598 }
599
600 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#[derive(Debug, Clone)]
632pub struct LinearStatistics {
633 pub total: usize,
635 pub used: usize,
637 pub unused: usize,
639 pub moved: usize,
641}
642
643#[derive(Debug, Clone, Default)]
645pub struct LinearTypeRegistry {
646 types: HashMap<String, LinearType>,
648}
649
650impl LinearTypeRegistry {
651 pub fn new() -> Self {
653 LinearTypeRegistry {
654 types: HashMap::new(),
655 }
656 }
657
658 pub fn with_builtins() -> Self {
660 let mut registry = LinearTypeRegistry::new();
661
662 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 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 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 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 pub fn register(&mut self, ty: LinearType) {
699 let name = ty.type_name().to_string();
700 self.types.insert(name, ty);
701 }
702
703 pub fn get(&self, name: &str) -> Option<&LinearType> {
705 self.types.get(name)
706 }
707
708 pub fn contains(&self, name: &str) -> bool {
710 self.types.contains_key(name)
711 }
712
713 pub fn type_names(&self) -> Vec<&str> {
715 self.types.keys().map(|s| s.as_str()).collect()
716 }
717
718 pub fn len(&self) -> usize {
720 self.types.len()
721 }
722
723 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 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 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}