@@ -8,7 +8,7 @@ use proc_macro2::TokenStream;
88use quote:: quote;
99use sha2:: { Digest , Sha256 } ;
1010use stellar_xdr:: curr as stellar_xdr;
11- use stellar_xdr:: { ScSpecEntry , ScSpecTypeDef , ScSpecUdtUnionCaseV0 } ;
11+ use stellar_xdr:: { ScSpecEntry , ScSpecTypeDef , ScSpecTypeUdt , ScSpecUdtUnionCaseV0 } ;
1212use syn:: Error ;
1313
1414use soroban_spec:: read:: { from_wasm, FromWasmError } ;
@@ -100,7 +100,7 @@ pub fn generate_without_file_with_options(
100100 specs : & [ ScSpecEntry ] ,
101101 opts : & GenerateOptions ,
102102) -> TokenStream {
103- let specs = apply_error_udt_fallback ( specs) ;
103+ let specs = apply_error_udt_override ( specs) ;
104104 let specs: & [ ScSpecEntry ] = & specs;
105105
106106 let mut spec_fns = Vec :: new ( ) ;
@@ -152,40 +152,48 @@ pub fn generate_without_file_with_options(
152152 }
153153}
154154
155- /// The `#[contractimpl]` macro emits any type named `Error` as a UDT reference
156- /// in the spec, so a contract that uses `soroban_sdk::Error` directly (rather
157- /// than defining its own error enum) produces a spec with
158- /// `Udt { name: "Error" }` but no matching error-enum entry. When that
159- /// happens, rewrite those references to the built-in `Error` type so the
160- /// generated client code references `soroban_sdk::Error`.
155+ /// The `#[contractimpl]` macro emits any type named `Error` in a contract's
156+ /// function signatures as the built-in `ScSpecTypeDef::Error` in the spec,
157+ /// regardless of whether the contract defined its own error enum named `Error`
158+ /// or used `soroban_sdk::Error` directly. To let clients of contracts that
159+ /// define their own `Error` enum see the user-defined type instead of
160+ /// `soroban_sdk::Error`, this pass rewrites every `ScSpecTypeDef::Error`
161+ /// reference in the spec to `Udt { name: "Error" }` whenever the spec also
162+ /// contains a `UdtErrorEnumV0` named `Error`.
163+ ///
164+ /// This keeps the on-the-wire spec format unchanged (so already-deployed
165+ /// contracts benefit without redeployment) and shifts the resolution to the
166+ /// client generator.
161167///
162168/// Returns a borrowed slice when no rewrite is needed, otherwise a
163169/// freshly-owned `Vec` with the rewrite applied.
164- fn apply_error_udt_fallback ( specs : & [ ScSpecEntry ] ) -> Cow < ' _ , [ ScSpecEntry ] > {
170+ fn apply_error_udt_override ( specs : & [ ScSpecEntry ] ) -> Cow < ' _ , [ ScSpecEntry ] > {
165171 let has_error_udt = specs. iter ( ) . any ( |e| {
166172 matches ! (
167173 e,
168174 ScSpecEntry :: UdtErrorEnumV0 ( err) if err. name. to_utf8_string_lossy( ) == "Error"
169175 )
170176 } ) ;
171177 if has_error_udt {
172- Cow :: Borrowed ( specs)
173- } else {
174178 let mut v = specs. to_vec ( ) ;
175- rewrite_missing_error_udt ( & mut v) ;
179+ rewrite_error_to_udt ( & mut v) ;
176180 Cow :: Owned ( v)
181+ } else {
182+ Cow :: Borrowed ( specs)
177183 }
178184}
179185
180- /// Rewrites every `ScSpecTypeDef::Udt { name: " Error" } ` reference in the
181- /// given entries to `ScSpecTypeDef::Error`. Called only when the spec has no
182- /// user-defined error enum named `Error`, so any such reference must refer to
183- /// the built-in `soroban_sdk::Error` type .
184- fn rewrite_missing_error_udt ( entries : & mut [ ScSpecEntry ] ) {
186+ /// Rewrites every `ScSpecTypeDef::Error` reference in the given entries to
187+ /// `ScSpecTypeDef::Udt { name: " Error" } `. Called only when the spec contains
188+ /// a user-defined error enum named `Error`, so the UDT reference resolves to
189+ /// that enum during code generation .
190+ fn rewrite_error_to_udt ( entries : & mut [ ScSpecEntry ] ) {
185191 fn rewrite_ty ( t : & mut ScSpecTypeDef ) {
186192 match t {
187- ScSpecTypeDef :: Udt ( u) if u. name . to_utf8_string_lossy ( ) == "Error" => {
188- * t = ScSpecTypeDef :: Error ;
193+ ScSpecTypeDef :: Error => {
194+ * t = ScSpecTypeDef :: Udt ( ScSpecTypeUdt {
195+ name : "Error" . try_into ( ) . unwrap ( ) ,
196+ } ) ;
189197 }
190198 ScSpecTypeDef :: Option ( o) => rewrite_ty ( & mut o. value_type ) ,
191199 ScSpecTypeDef :: Result ( r) => {
@@ -344,7 +352,11 @@ pub enum MyError {
344352 }
345353
346354 /// Test that shows the raw spec entries from the wasm.
347- /// Verifies that both Error and MyError are correctly represented as UDTs.
355+ /// Verifies that the on-the-wire spec format is unchanged: a contract
356+ /// error enum named `Error` is still emitted as the built-in
357+ /// `ScSpecTypeDef::Error` in function signatures (the user-defined-vs-SDK
358+ /// disambiguation happens at client generation time, not here). A
359+ /// differently-named error enum (`MyError`) is emitted as a UDT reference.
348360 #[ test]
349361 fn test_add_u64_spec_entries ( ) {
350362 use super :: ScSpecEntry ;
@@ -371,16 +383,10 @@ pub enum MyError {
371383 matches!( r. ok_type. as_ref( ) , ScSpecTypeDef :: U64 ) ,
372384 "ok_type should be U64"
373385 ) ;
374- let ScSpecTypeDef :: Udt ( u) = r. error_type . as_ref ( ) else {
375- panic ! (
376- "error_type should be a UDT for Error, got {:?}" ,
377- r. error_type
378- ) ;
379- } ;
380- assert_eq ! (
381- u. name. to_utf8_string( ) . unwrap( ) ,
382- "Error" ,
383- "error_type should be Error UDT"
386+ assert ! (
387+ matches!( r. error_type. as_ref( ) , ScSpecTypeDef :: Error ) ,
388+ "error_type should be the built-in Error in the wasm spec, got {:?}" ,
389+ r. error_type
384390 ) ;
385391
386392 // Find the safe_add_two function spec
@@ -420,25 +426,23 @@ pub enum MyError {
420426 ) ;
421427 }
422428
423- /// If the spec references `Udt { name: " Error" }` but no error enum
424- /// named `Error` is defined , the generator must fall back to
425- /// `soroban_sdk::Error` so the generated client code still compiles.
426- /// This covers the (rare) case where a contract uses `soroban_sdk::Error`
427- /// directly as its Result error type instead of defining its own enum .
429+ /// When the spec references `ScSpecTypeDef:: Error` and contains no error
430+ /// enum named `Error`, the generator must leave it as `soroban_sdk::Error`.
431+ /// This covers contracts that use `soroban_sdk::Error` directly as their
432+ /// Result error type, including every contract compiled before the
433+ /// error-enum override was introduced .
428434 #[ test]
429435 fn test_missing_error_udt_falls_back_to_sdk_error ( ) {
430436 use super :: ScSpecEntry ;
431- use stellar_xdr:: curr:: { ScSpecFunctionV0 , ScSpecTypeDef , ScSpecTypeResult , ScSpecTypeUdt } ;
437+ use stellar_xdr:: curr:: { ScSpecFunctionV0 , ScSpecTypeDef , ScSpecTypeResult } ;
432438
433439 let func = ScSpecFunctionV0 {
434440 doc : "" . try_into ( ) . unwrap ( ) ,
435441 name : "safe_add" . try_into ( ) . unwrap ( ) ,
436442 inputs : [ ] . try_into ( ) . unwrap ( ) ,
437443 outputs : [ ScSpecTypeDef :: Result ( Box :: new ( ScSpecTypeResult {
438444 ok_type : Box :: new ( ScSpecTypeDef :: U64 ) ,
439- error_type : Box :: new ( ScSpecTypeDef :: Udt ( ScSpecTypeUdt {
440- name : "Error" . try_into ( ) . unwrap ( ) ,
441- } ) ) ,
445+ error_type : Box :: new ( ScSpecTypeDef :: Error ) ,
442446 } ) ) ]
443447 . try_into ( )
444448 . unwrap ( ) ,
@@ -459,14 +463,15 @@ pub trait Contract {
459463 ) ;
460464 }
461465
462- /// When the spec *does* contain a user-defined `Error` error enum, the
463- /// UDT reference must be preserved and not rewritten to `soroban_sdk::Error`.
466+ /// When the spec contains a user-defined `Error` error enum, every
467+ /// `ScSpecTypeDef::Error` reference in the spec must be rewritten to
468+ /// reference that UDT instead of `soroban_sdk::Error`.
464469 #[ test]
465- fn test_error_udt_preserved_when_defined ( ) {
470+ fn test_error_udt_overrides_sdk_error ( ) {
466471 use super :: ScSpecEntry ;
467472 use stellar_xdr:: curr:: {
468- ScSpecFunctionV0 , ScSpecTypeDef , ScSpecTypeResult , ScSpecTypeUdt ,
469- ScSpecUdtErrorEnumCaseV0 , ScSpecUdtErrorEnumV0 ,
473+ ScSpecFunctionV0 , ScSpecTypeDef , ScSpecTypeResult , ScSpecUdtErrorEnumCaseV0 ,
474+ ScSpecUdtErrorEnumV0 ,
470475 } ;
471476
472477 let func = ScSpecFunctionV0 {
@@ -475,9 +480,7 @@ pub trait Contract {
475480 inputs : [ ] . try_into ( ) . unwrap ( ) ,
476481 outputs : [ ScSpecTypeDef :: Result ( Box :: new ( ScSpecTypeResult {
477482 ok_type : Box :: new ( ScSpecTypeDef :: U64 ) ,
478- error_type : Box :: new ( ScSpecTypeDef :: Udt ( ScSpecTypeUdt {
479- name : "Error" . try_into ( ) . unwrap ( ) ,
480- } ) ) ,
483+ error_type : Box :: new ( ScSpecTypeDef :: Error ) ,
481484 } ) ) ]
482485 . try_into ( )
483486 . unwrap ( ) ,
0 commit comments