@@ -5,7 +5,7 @@ use hugr_model::v0::table;
55use itertools:: { Either , Itertools as _} ;
66
77use crate :: HugrView as _;
8- use crate :: envelope:: description:: PackageDesc ;
8+ use crate :: envelope:: description:: { ExtensionDesc , ModuleDesc , PackageDesc } ;
99use crate :: envelope:: header:: { EnvelopeFormat , HeaderError } ;
1010use crate :: envelope:: {
1111 EnvelopeError , EnvelopeHeader , ExtensionBreakingError , FormatUnsupportedError ,
@@ -103,6 +103,32 @@ impl<R: BufRead> EnvelopeReader<R> {
103103 self . registry . extend ( extensions) ;
104104 }
105105
106+ /// Handle extension resolution errors by recording missing extensions in the description.
107+ ///
108+ /// This function inspects the error and adds any missing extensions to the module description
109+ /// with a default version of 0.0.0.
110+ fn handle_resolution_error ( desc : & mut ModuleDesc , err : & ExtensionResolutionError ) {
111+ match err {
112+ ExtensionResolutionError :: MissingOpExtension {
113+ missing_extension, ..
114+ }
115+ | ExtensionResolutionError :: MissingTypeExtension {
116+ missing_extension, ..
117+ } => desc. extend_used_extensions_resolved ( [ ExtensionDesc :: new (
118+ missing_extension,
119+ crate :: extension:: Version :: new ( 0 , 0 , 0 ) ,
120+ ) ] ) ,
121+ ExtensionResolutionError :: InvalidConstTypes {
122+ missing_extensions, ..
123+ } => desc. extend_used_extensions_resolved (
124+ missing_extensions
125+ . iter ( )
126+ . map ( |ext| ExtensionDesc :: new ( ext, crate :: extension:: Version :: new ( 0 , 0 , 0 ) ) ) ,
127+ ) ,
128+ _ => { }
129+ }
130+ }
131+
106132 fn read_impl ( & mut self ) -> Result < Package , PayloadError > {
107133 let mut package = match self . header ( ) . format {
108134 EnvelopeFormat :: PackageJson => self . decode_json ( ) ?,
@@ -121,7 +147,10 @@ impl<R: BufRead> EnvelopeReader<R> {
121147 check_breaking_extensions ( module. extensions ( ) , used_exts. drain ( ..) ) ?;
122148 }
123149
124- module. resolve_extension_defs ( & self . registry ) ?;
150+ module
151+ . resolve_extension_defs ( & self . registry )
152+ . inspect_err ( |err| Self :: handle_resolution_error ( desc, err) ) ?;
153+
125154 // overwrite the description with the actual module read,
126155 // cheap so ok to repeat.
127156 desc. load_from_hugr ( & module) ;
@@ -415,4 +444,77 @@ mod test {
415444 assert_eq ! ( description. header, header) ;
416445 assert_eq ! ( description. n_modules( ) , 0 ) ; // No valid modules should be set
417446 }
447+
448+ #[ test]
449+ fn test_handle_resolution_error ( ) {
450+ use crate :: extension:: ExtensionId ;
451+ use crate :: ops:: { OpName , constant:: ValueName } ;
452+ use crate :: types:: TypeName ;
453+
454+ let mut desc = ModuleDesc :: default ( ) ;
455+ let handle_error = |d : & mut ModuleDesc , err : & ExtensionResolutionError | {
456+ EnvelopeReader :: < Cursor < Vec < u8 > > > :: handle_resolution_error ( d, err)
457+ } ;
458+ let assert_extensions = |d : & ModuleDesc , expected_ids : & [ & ExtensionId ] | {
459+ let resolved = d. used_extensions_resolved . as_ref ( ) . unwrap ( ) ;
460+ assert_eq ! ( resolved. len( ) , expected_ids. len( ) ) ;
461+ let names: Vec < _ > = resolved. iter ( ) . map ( |e| & e. name ) . collect ( ) ;
462+ for ext_id in expected_ids {
463+ assert ! ( names. contains( &&ext_id. to_string( ) ) ) ;
464+ }
465+ assert ! (
466+ resolved
467+ . iter( )
468+ . all( |e| e. version == crate :: extension:: Version :: new( 0 , 0 , 0 ) )
469+ ) ;
470+ } ;
471+
472+ // Test MissingOpExtension
473+ let ext_id = ExtensionId :: new ( "test.extension" ) . unwrap ( ) ;
474+ let error = ExtensionResolutionError :: MissingOpExtension {
475+ node : None ,
476+ op : OpName :: new ( "test.op" ) ,
477+ missing_extension : ext_id. clone ( ) ,
478+ available_extensions : vec ! [ ] ,
479+ } ;
480+ handle_error ( & mut desc, & error) ;
481+ assert_extensions ( & desc, & [ & ext_id] ) ;
482+
483+ // Test MissingTypeExtension
484+ desc. used_extensions_resolved = None ;
485+ let ext_id2 = ExtensionId :: new ( "test.extension2" ) . unwrap ( ) ;
486+ let error = ExtensionResolutionError :: MissingTypeExtension {
487+ node : None ,
488+ ty : TypeName :: new ( "test.type" ) ,
489+ missing_extension : ext_id2. clone ( ) ,
490+ available_extensions : vec ! [ ] ,
491+ } ;
492+ handle_error ( & mut desc, & error) ;
493+ assert_extensions ( & desc, & [ & ext_id2] ) ;
494+
495+ // Test InvalidConstTypes with multiple extensions
496+ desc. used_extensions_resolved = None ;
497+ let ext_id3 = ExtensionId :: new ( "test.extension3" ) . unwrap ( ) ;
498+ let ext_id4 = ExtensionId :: new ( "test.extension4" ) . unwrap ( ) ;
499+ let mut missing_exts = crate :: extension:: ExtensionSet :: new ( ) ;
500+ missing_exts. insert ( ext_id3. clone ( ) ) ;
501+ missing_exts. insert ( ext_id4. clone ( ) ) ;
502+
503+ let error = ExtensionResolutionError :: InvalidConstTypes {
504+ value : ValueName :: new ( "test.value" ) ,
505+ missing_extensions : missing_exts,
506+ } ;
507+ handle_error ( & mut desc, & error) ;
508+ assert_extensions ( & desc, & [ & ext_id3, & ext_id4] ) ;
509+
510+ // Test other error variant (should not add anything)
511+ desc. used_extensions_resolved = None ;
512+ let error = ExtensionResolutionError :: WrongTypeDefExtension {
513+ extension : ExtensionId :: new ( "ext1" ) . unwrap ( ) ,
514+ def : TypeName :: new ( "def" ) ,
515+ wrong_extension : ExtensionId :: new ( "ext2" ) . unwrap ( ) ,
516+ } ;
517+ handle_error ( & mut desc, & error) ;
518+ assert ! ( desc. used_extensions_resolved. is_none( ) ) ;
519+ }
418520}
0 commit comments