foundry_compilers_artifacts_solc/
output_selection.rs

1//! Bindings for standard json output selection.
2
3use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
4use std::{collections::BTreeMap, fmt, str::FromStr};
5
6/// Represents the desired outputs based on a File `(file -> (contract -> [outputs]))`
7pub type FileOutputSelection = BTreeMap<String, Vec<String>>;
8
9/// Represents the selected output of files and contracts.
10///
11/// The first level key is the file name and the second level key is the
12/// contract name. An empty contract name is used for outputs that are
13/// not tied to a contract but to the whole source file like the AST.
14/// A star as contract name refers to all contracts in the file.
15/// Similarly, a star as a file name matches all files.
16/// To select all outputs the compiler can possibly generate, use
17/// "outputSelection: { "*": { "*": [ "*" ], "": [ "*" ] } }"
18/// but note that this might slow down the compilation process needlessly.
19///
20/// The available output types are as follows:
21///
22/// File level (needs empty string as contract name):
23///   ast - AST of all source files
24///
25/// Contract level (needs the contract name or "*"):
26///   abi - ABI
27///   devdoc - Developer documentation (natspec)
28///   userdoc - User documentation (natspec)
29///   metadata - Metadata
30///   ir - Yul intermediate representation of the code before optimization
31///   irOptimized - Intermediate representation after optimization
32///   storageLayout - Slots, offsets and types of the contract's state
33///     variables.
34///   evm.assembly - New assembly format
35///   evm.legacyAssembly - Old-style assembly format in JSON
36///   evm.bytecode.functionDebugData - Debugging information at function level
37///   evm.bytecode.object - Bytecode object
38///   evm.bytecode.opcodes - Opcodes list
39///   evm.bytecode.sourceMap - Source mapping (useful for debugging)
40///   evm.bytecode.linkReferences - Link references (if unlinked object)
41///   evm.bytecode.generatedSources - Sources generated by the compiler
42///   evm.deployedBytecode* - Deployed bytecode (has all the options that
43///     evm.bytecode has)
44///   evm.deployedBytecode.immutableReferences - Map from AST ids to
45///     bytecode ranges that reference immutables
46///   evm.methodIdentifiers - The list of function hashes
47///   evm.gasEstimates - Function gas estimates
48///   ewasm.wast - Ewasm in WebAssembly S-expressions format
49///   ewasm.wasm - Ewasm in WebAssembly binary format
50///
51/// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select
52/// every target part of that output. Additionally, `*` can be used as a
53/// wildcard to request everything.
54#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
55#[serde(transparent)]
56pub struct OutputSelection(pub BTreeMap<String, FileOutputSelection>);
57
58impl OutputSelection {
59    /// select all outputs the compiler can possibly generate, use
60    /// `{ "*": { "*": [ "*" ], "": [ "*" ] } }`
61    /// but note that this might slow down the compilation process needlessly.
62    pub fn complete_output_selection() -> Self {
63        BTreeMap::from([(
64            "*".to_string(),
65            BTreeMap::from([
66                ("*".to_string(), vec!["*".to_string()]),
67                (String::new(), vec!["*".to_string()]),
68            ]),
69        )])
70        .into()
71    }
72
73    /// Default output selection.
74    pub fn default_output_selection() -> Self {
75        BTreeMap::from([("*".to_string(), Self::default_file_output_selection())]).into()
76    }
77
78    /// Default file output selection.
79    ///
80    /// Uses [`ContractOutputSelection::basic`].
81    pub fn default_file_output_selection() -> FileOutputSelection {
82        BTreeMap::from([(
83            "*".to_string(),
84            ContractOutputSelection::basic().iter().map(ToString::to_string).collect(),
85        )])
86    }
87
88    /// Returns output selection configuration which enables the same provided outputs for each
89    /// contract of each source.
90    pub fn common_output_selection(outputs: impl IntoIterator<Item = String>) -> Self {
91        BTreeMap::from([(
92            "*".to_string(),
93            BTreeMap::from([("*".to_string(), outputs.into_iter().collect())]),
94        )])
95        .into()
96    }
97
98    /// Returns an empty output selection which corresponds to an empty map `{}`
99    pub fn empty_file_output_select() -> FileOutputSelection {
100        Default::default()
101    }
102
103    /// Returns output selection which requests only AST for all sources.
104    pub fn ast_output_selection() -> Self {
105        BTreeMap::from([(
106            "*".to_string(),
107            BTreeMap::from([
108                // Do not request any output for separate contracts
109                ("*".to_string(), vec![]),
110                // Request AST for all sources.
111                (String::new(), vec!["ast".to_string()]),
112            ]),
113        )])
114        .into()
115    }
116
117    /// Returns true if this output selection is a subset of the other output selection.
118    // TODO: correctly process wildcard keys to reduce false negatives
119    pub fn is_subset_of(&self, other: &Self) -> bool {
120        self.0.iter().all(|(file, selection)| {
121            other.0.get(file).is_some_and(|other_selection| {
122                selection.iter().all(|(contract, outputs)| {
123                    other_selection.get(contract).is_some_and(|other_outputs| {
124                        outputs.iter().all(|output| other_outputs.contains(output))
125                    })
126                })
127            })
128        })
129    }
130
131    /// Returns true if AST output is requested.
132    ///
133    /// AST is a file-level output (uses empty string as contract name) and requires
134    /// all files to be compiled together to ensure consistent node IDs across the
135    /// compilation unit. When AST is requested, sparse output optimization should
136    /// be disabled.
137    pub fn contains_ast(&self) -> bool {
138        self.0.values().any(|file_selection| {
139            file_selection.iter().any(|(contract_name, outputs)| {
140                // AST is a file-level output, indicated by an empty contract name.
141                // When contract_name is empty, outputs like "ast" or "*" include AST.
142                // When contract_name is "*", the "*" in outputs means "all contract outputs"
143                // which doesn't include AST (file-level), but explicit "ast" would be invalid.
144                contract_name.is_empty() && outputs.iter().any(|o| o == "ast" || o == "*")
145            })
146        })
147    }
148}
149
150// this will make sure that if the `FileOutputSelection` for a certain file is empty will be
151// serializes as `"*" : []` because
152// > Contract level (needs the contract name or "*") <https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html>
153impl Serialize for OutputSelection {
154    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155    where
156        S: Serializer,
157    {
158        struct EmptyFileOutput;
159
160        impl Serialize for EmptyFileOutput {
161            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162            where
163                S: Serializer,
164            {
165                let mut map = serializer.serialize_map(Some(1))?;
166                map.serialize_entry("*", &[] as &[String])?;
167                map.end()
168            }
169        }
170
171        let mut map = serializer.serialize_map(Some(self.0.len()))?;
172        for (file, selection) in self.0.iter() {
173            if selection.is_empty() {
174                map.serialize_entry(file, &EmptyFileOutput {})?;
175            } else {
176                map.serialize_entry(file, selection)?;
177            }
178        }
179        map.end()
180    }
181}
182
183impl AsRef<BTreeMap<String, FileOutputSelection>> for OutputSelection {
184    fn as_ref(&self) -> &BTreeMap<String, FileOutputSelection> {
185        &self.0
186    }
187}
188
189impl AsMut<BTreeMap<String, FileOutputSelection>> for OutputSelection {
190    fn as_mut(&mut self) -> &mut BTreeMap<String, FileOutputSelection> {
191        &mut self.0
192    }
193}
194
195impl From<BTreeMap<String, FileOutputSelection>> for OutputSelection {
196    fn from(s: BTreeMap<String, FileOutputSelection>) -> Self {
197        Self(s)
198    }
199}
200
201/// Contract level output selection
202#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
203pub enum ContractOutputSelection {
204    Abi,
205    DevDoc,
206    UserDoc,
207    Metadata,
208    Ir,
209    IrOptimized,
210    IrOptimizedAst,
211    StorageLayout,
212    TransientStorageLayout,
213    Evm(EvmOutputSelection),
214    Ewasm(EwasmOutputSelection),
215}
216
217impl ContractOutputSelection {
218    /// Returns the basic set of contract level settings that should be included in the `Contract`
219    /// that solc emits.
220    ///
221    /// These correspond to the fields in `CompactBytecode`, `CompactDeployedBytecode`, ABI, and
222    /// method identifiers.
223    pub fn basic() -> Vec<Self> {
224        // We don't include all the `bytecode` fields because `generatedSources` is a massive JSON
225        // object and is not used by Foundry.
226        vec![
227            Self::Abi,
228            // The fields in `CompactBytecode`.
229            BytecodeOutputSelection::Object.into(),
230            BytecodeOutputSelection::SourceMap.into(),
231            BytecodeOutputSelection::LinkReferences.into(),
232            // The fields in `CompactDeployedBytecode`.
233            DeployedBytecodeOutputSelection::Object.into(),
234            DeployedBytecodeOutputSelection::SourceMap.into(),
235            DeployedBytecodeOutputSelection::LinkReferences.into(),
236            DeployedBytecodeOutputSelection::ImmutableReferences.into(),
237            EvmOutputSelection::MethodIdentifiers.into(),
238        ]
239    }
240}
241
242impl Serialize for ContractOutputSelection {
243    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244    where
245        S: Serializer,
246    {
247        serializer.collect_str(self)
248    }
249}
250
251impl<'de> Deserialize<'de> for ContractOutputSelection {
252    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253    where
254        D: Deserializer<'de>,
255    {
256        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
257    }
258}
259
260impl fmt::Display for ContractOutputSelection {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            Self::Abi => f.write_str("abi"),
264            Self::DevDoc => f.write_str("devdoc"),
265            Self::UserDoc => f.write_str("userdoc"),
266            Self::Metadata => f.write_str("metadata"),
267            Self::Ir => f.write_str("ir"),
268            Self::IrOptimized => f.write_str("irOptimized"),
269            Self::IrOptimizedAst => f.write_str("irOptimizedAst"),
270            Self::StorageLayout => f.write_str("storageLayout"),
271            Self::TransientStorageLayout => f.write_str("transientStorageLayout"),
272            Self::Evm(e) => e.fmt(f),
273            Self::Ewasm(e) => e.fmt(f),
274        }
275    }
276}
277
278impl FromStr for ContractOutputSelection {
279    type Err = String;
280
281    fn from_str(s: &str) -> Result<Self, Self::Err> {
282        match s {
283            "abi" => Ok(Self::Abi),
284            "devdoc" => Ok(Self::DevDoc),
285            "userdoc" => Ok(Self::UserDoc),
286            "metadata" => Ok(Self::Metadata),
287            "ir" => Ok(Self::Ir),
288            "ir-optimized" | "irOptimized" | "iroptimized" => Ok(Self::IrOptimized),
289            "irOptimizedAst" | "ir-optimized-ast" | "iroptimizedast" => Ok(Self::IrOptimizedAst),
290            "storage-layout" | "storagelayout" | "storageLayout" => Ok(Self::StorageLayout),
291            "transient-storage-layout" | "transientstoragelayout" | "transientStorageLayout" => {
292                Ok(Self::TransientStorageLayout)
293            }
294            s => EvmOutputSelection::from_str(s)
295                .map(ContractOutputSelection::Evm)
296                .or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
297                .map_err(|_| format!("Invalid contract output selection: {s}")),
298        }
299    }
300}
301
302impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
303    fn from(evm: T) -> Self {
304        Self::Evm(evm.into())
305    }
306}
307
308impl From<EwasmOutputSelection> for ContractOutputSelection {
309    fn from(ewasm: EwasmOutputSelection) -> Self {
310        Self::Ewasm(ewasm)
311    }
312}
313
314/// Contract level output selection for `evm`
315#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
316pub enum EvmOutputSelection {
317    All,
318    Assembly,
319    LegacyAssembly,
320    MethodIdentifiers,
321    GasEstimates,
322    ByteCode(BytecodeOutputSelection),
323    DeployedByteCode(DeployedBytecodeOutputSelection),
324}
325
326impl From<BytecodeOutputSelection> for EvmOutputSelection {
327    fn from(b: BytecodeOutputSelection) -> Self {
328        Self::ByteCode(b)
329    }
330}
331
332impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
333    fn from(b: DeployedBytecodeOutputSelection) -> Self {
334        Self::DeployedByteCode(b)
335    }
336}
337
338impl Serialize for EvmOutputSelection {
339    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
340    where
341        S: Serializer,
342    {
343        serializer.collect_str(self)
344    }
345}
346
347impl<'de> Deserialize<'de> for EvmOutputSelection {
348    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
349    where
350        D: Deserializer<'de>,
351    {
352        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
353    }
354}
355
356impl fmt::Display for EvmOutputSelection {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        match self {
359            Self::All => f.write_str("evm"),
360            Self::Assembly => f.write_str("evm.assembly"),
361            Self::LegacyAssembly => f.write_str("evm.legacyAssembly"),
362            Self::MethodIdentifiers => f.write_str("evm.methodIdentifiers"),
363            Self::GasEstimates => f.write_str("evm.gasEstimates"),
364            Self::ByteCode(b) => b.fmt(f),
365            Self::DeployedByteCode(b) => b.fmt(f),
366        }
367    }
368}
369
370impl FromStr for EvmOutputSelection {
371    type Err = String;
372
373    fn from_str(s: &str) -> Result<Self, Self::Err> {
374        match s {
375            "evm" => Ok(Self::All),
376            "asm" | "evm.assembly" => Ok(Self::Assembly),
377            "legacyAssembly" | "evm.legacyAssembly" => Ok(Self::LegacyAssembly),
378            "methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
379                Ok(Self::MethodIdentifiers)
380            }
381            "gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(Self::GasEstimates),
382            s => BytecodeOutputSelection::from_str(s)
383                .map(EvmOutputSelection::ByteCode)
384                .or_else(|_| {
385                    DeployedBytecodeOutputSelection::from_str(s)
386                        .map(EvmOutputSelection::DeployedByteCode)
387                })
388                .map_err(|_| format!("Invalid evm selection: {s}")),
389        }
390    }
391}
392
393/// Contract level output selection for `evm.bytecode`
394#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
395pub enum BytecodeOutputSelection {
396    All,
397    FunctionDebugData,
398    Object,
399    Opcodes,
400    SourceMap,
401    LinkReferences,
402    GeneratedSources,
403}
404
405impl Serialize for BytecodeOutputSelection {
406    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
407    where
408        S: Serializer,
409    {
410        serializer.collect_str(self)
411    }
412}
413
414impl<'de> Deserialize<'de> for BytecodeOutputSelection {
415    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
416    where
417        D: Deserializer<'de>,
418    {
419        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
420    }
421}
422
423impl fmt::Display for BytecodeOutputSelection {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        match self {
426            Self::All => f.write_str("evm.bytecode"),
427            Self::FunctionDebugData => f.write_str("evm.bytecode.functionDebugData"),
428            Self::Object => f.write_str("evm.bytecode.object"),
429            Self::Opcodes => f.write_str("evm.bytecode.opcodes"),
430            Self::SourceMap => f.write_str("evm.bytecode.sourceMap"),
431            Self::LinkReferences => f.write_str("evm.bytecode.linkReferences"),
432            Self::GeneratedSources => f.write_str("evm.bytecode.generatedSources"),
433        }
434    }
435}
436
437impl FromStr for BytecodeOutputSelection {
438    type Err = String;
439
440    fn from_str(s: &str) -> Result<Self, Self::Err> {
441        match s {
442            "evm.bytecode" => Ok(Self::All),
443            "evm.bytecode.functionDebugData" => Ok(Self::FunctionDebugData),
444            "code" | "bin" | "evm.bytecode.object" => Ok(Self::Object),
445            "evm.bytecode.opcodes" => Ok(Self::Opcodes),
446            "evm.bytecode.sourceMap" => Ok(Self::SourceMap),
447            "evm.bytecode.linkReferences" => Ok(Self::LinkReferences),
448            "evm.bytecode.generatedSources" => Ok(Self::GeneratedSources),
449            s => Err(format!("Invalid bytecode selection: {s}")),
450        }
451    }
452}
453
454/// Contract level output selection for `evm.deployedBytecode`
455#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
456pub enum DeployedBytecodeOutputSelection {
457    All,
458    FunctionDebugData,
459    Object,
460    Opcodes,
461    SourceMap,
462    LinkReferences,
463    GeneratedSources,
464    ImmutableReferences,
465}
466
467impl Serialize for DeployedBytecodeOutputSelection {
468    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
469    where
470        S: Serializer,
471    {
472        serializer.collect_str(self)
473    }
474}
475
476impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection {
477    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
478    where
479        D: Deserializer<'de>,
480    {
481        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
482    }
483}
484
485impl fmt::Display for DeployedBytecodeOutputSelection {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        match self {
488            Self::All => f.write_str("evm.deployedBytecode"),
489            Self::FunctionDebugData => f.write_str("evm.deployedBytecode.functionDebugData"),
490            Self::Object => f.write_str("evm.deployedBytecode.object"),
491            Self::Opcodes => f.write_str("evm.deployedBytecode.opcodes"),
492            Self::SourceMap => f.write_str("evm.deployedBytecode.sourceMap"),
493            Self::LinkReferences => f.write_str("evm.deployedBytecode.linkReferences"),
494            Self::GeneratedSources => f.write_str("evm.deployedBytecode.generatedSources"),
495            Self::ImmutableReferences => f.write_str("evm.deployedBytecode.immutableReferences"),
496        }
497    }
498}
499
500impl FromStr for DeployedBytecodeOutputSelection {
501    type Err = String;
502
503    fn from_str(s: &str) -> Result<Self, Self::Err> {
504        match s {
505            "evm.deployedBytecode" => Ok(Self::All),
506            "evm.deployedBytecode.functionDebugData" => Ok(Self::FunctionDebugData),
507            "deployed-code"
508            | "deployed-bin"
509            | "runtime-code"
510            | "runtime-bin"
511            | "evm.deployedBytecode.object" => Ok(Self::Object),
512            "evm.deployedBytecode.opcodes" => Ok(Self::Opcodes),
513            "evm.deployedBytecode.sourceMap" => Ok(Self::SourceMap),
514            "evm.deployedBytecode.linkReferences" => Ok(Self::LinkReferences),
515            "evm.deployedBytecode.generatedSources" => Ok(Self::GeneratedSources),
516            "evm.deployedBytecode.immutableReferences" => Ok(Self::ImmutableReferences),
517            s => Err(format!("Invalid deployedBytecode selection: {s}")),
518        }
519    }
520}
521
522/// Contract level output selection for `evm.ewasm`
523#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
524pub enum EwasmOutputSelection {
525    All,
526    Wast,
527    Wasm,
528}
529
530impl Serialize for EwasmOutputSelection {
531    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532    where
533        S: Serializer,
534    {
535        serializer.collect_str(self)
536    }
537}
538
539impl<'de> Deserialize<'de> for EwasmOutputSelection {
540    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
541    where
542        D: Deserializer<'de>,
543    {
544        String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
545    }
546}
547
548impl fmt::Display for EwasmOutputSelection {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        match self {
551            Self::All => f.write_str("ewasm"),
552            Self::Wast => f.write_str("ewasm.wast"),
553            Self::Wasm => f.write_str("ewasm.wasm"),
554        }
555    }
556}
557
558impl FromStr for EwasmOutputSelection {
559    type Err = String;
560
561    fn from_str(s: &str) -> Result<Self, Self::Err> {
562        match s {
563            "ewasm" => Ok(Self::All),
564            "ewasm.wast" => Ok(Self::Wast),
565            "ewasm.wasm" => Ok(Self::Wasm),
566            s => Err(format!("Invalid ewasm selection: {s}")),
567        }
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[test]
576    fn outputselection_serde_works() {
577        let mut output = BTreeMap::default();
578        output.insert(
579            "*".to_string(),
580            vec![
581                "abi".to_string(),
582                "evm.bytecode".to_string(),
583                "evm.deployedBytecode".to_string(),
584                "evm.methodIdentifiers".to_string(),
585            ],
586        );
587
588        let json = serde_json::to_string(&output).unwrap();
589        let deserde_selection: BTreeMap<String, Vec<ContractOutputSelection>> =
590            serde_json::from_str(&json).unwrap();
591
592        assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
593    }
594
595    #[test]
596    fn empty_outputselection_serde_works() {
597        let mut empty = OutputSelection::default();
598        empty.0.insert("contract.sol".to_string(), OutputSelection::empty_file_output_select());
599        let s = serde_json::to_string(&empty).unwrap();
600        assert_eq!(s, r#"{"contract.sol":{"*":[]}}"#);
601    }
602
603    #[test]
604    fn outputselection_subset_of() {
605        let output_selection = OutputSelection::from(BTreeMap::from([(
606            "*".to_string(),
607            BTreeMap::from([(
608                "*".to_string(),
609                vec!["abi".to_string(), "evm.bytecode".to_string()],
610            )]),
611        )]));
612
613        let output_selection_abi = OutputSelection::from(BTreeMap::from([(
614            "*".to_string(),
615            BTreeMap::from([("*".to_string(), vec!["abi".to_string()])]),
616        )]));
617
618        assert!(output_selection_abi.is_subset_of(&output_selection));
619        assert!(!output_selection.is_subset_of(&output_selection_abi));
620
621        let output_selection_empty = OutputSelection::from(BTreeMap::from([(
622            "*".to_string(),
623            BTreeMap::from([("*".to_string(), vec![])]),
624        )]));
625
626        assert!(output_selection_empty.is_subset_of(&output_selection));
627        assert!(output_selection_empty.is_subset_of(&output_selection_abi));
628        assert!(!output_selection.is_subset_of(&output_selection_empty));
629        assert!(!output_selection_abi.is_subset_of(&output_selection_empty));
630
631        let output_selection_specific = OutputSelection::from(BTreeMap::from([(
632            "Contract.sol".to_string(),
633            BTreeMap::from([(
634                "Contract".to_string(),
635                vec![
636                    "abi".to_string(),
637                    "evm.bytecode".to_string(),
638                    "evm.deployedBytecode".to_string(),
639                ],
640            )]),
641        )]));
642
643        assert!(!output_selection_specific.is_subset_of(&output_selection));
644    }
645
646    #[test]
647    fn deployed_bytecode_from_str() {
648        assert_eq!(
649            DeployedBytecodeOutputSelection::from_str("evm.deployedBytecode.immutableReferences")
650                .unwrap(),
651            DeployedBytecodeOutputSelection::ImmutableReferences
652        )
653    }
654
655    #[test]
656    fn test_contains_ast() {
657        // AST selection with empty contract name (file-level output)
658        let ast_selection = OutputSelection::ast_output_selection();
659        assert!(ast_selection.contains_ast());
660
661        // Default output selection (no AST)
662        let default_selection = OutputSelection::default_output_selection();
663        assert!(!default_selection.contains_ast());
664
665        // Complete output selection (includes "*" wildcard which includes AST)
666        let complete_selection = OutputSelection::complete_output_selection();
667        assert!(complete_selection.contains_ast());
668
669        // Empty output selection
670        let empty_selection = OutputSelection::from(BTreeMap::from([(
671            "*".to_string(),
672            BTreeMap::from([("*".to_string(), vec![])]),
673        )]));
674        assert!(!empty_selection.contains_ast());
675
676        // Explicit AST in file-level output
677        let explicit_ast = OutputSelection::from(BTreeMap::from([(
678            "*".to_string(),
679            BTreeMap::from([
680                ("*".to_string(), vec!["abi".to_string()]),
681                (String::new(), vec!["ast".to_string()]),
682            ]),
683        )]));
684        assert!(explicit_ast.contains_ast());
685
686        // Only contract-level outputs (no AST)
687        let contract_only = OutputSelection::from(BTreeMap::from([(
688            "*".to_string(),
689            BTreeMap::from([(
690                "*".to_string(),
691                vec!["abi".to_string(), "evm.bytecode".to_string()],
692            )]),
693        )]));
694        assert!(!contract_only.contains_ast());
695    }
696}