1use std::collections::{HashMap, HashSet};
2
3use super::Form;
4
5const FOUNDATION_LIBRARIES: &[(&str, &str, &str)] = &[
6 ("string", "std.foundation.string", "str"),
7 ("promise", "std.foundation.promise", "promise"),
8 ("bytes", "std.foundation.bytes", "bytes"),
9 ("coroutine", "std.foundation.coroutine", "co"),
10 ("pretty", "std.foundation.pretty", "pretty"),
11];
12
13pub(crate) fn foundation_library_alias(library: &str) -> Option<&'static str> {
14 FOUNDATION_LIBRARIES
15 .iter()
16 .find(|(name, _, _)| *name == library)
17 .map(|(_, _, alias)| *alias)
18}
19
20#[path = "generated/rewrite.rs"]
21mod rewrite;
22
23#[derive(Debug, Clone, Default)]
24pub struct GeneratedNamespaceConfig {
25 aliases: HashMap<String, String>,
26 global_alias: Option<String>,
27 global_aliases: HashMap<String, String>,
28 declared_global_imports: Vec<String>,
29 lazy_aliases: HashMap<String, String>,
30 refers: HashMap<String, String>,
31 macro_refers: HashMap<String, String>,
32 required_namespaces: Vec<String>,
33 used_namespaces: Vec<String>,
34 used_exclusions: HashMap<String, HashSet<String>>,
35 internal_access: HashSet<String>,
36 excluded_foundation_libraries: HashSet<String>,
37 excluded_foundation: HashSet<String>,
38 exposed_foundation: Option<HashSet<String>>,
39 native_flavor: Option<String>,
40 native_imports: Vec<(String, String)>,
41 native_flavor_imports: Vec<(String, String)>,
42 role: String,
43 blank: bool,
44}
45
46impl GeneratedNamespaceConfig {
47 pub fn defaults() -> Self {
48 Self {
49 aliases: HashMap::new(),
50 global_alias: None,
51 global_aliases: HashMap::new(),
52 declared_global_imports: Vec::new(),
53 lazy_aliases: HashMap::new(),
54 refers: HashMap::new(),
55 macro_refers: HashMap::new(),
56 required_namespaces: Vec::new(),
57 used_namespaces: Vec::new(),
58 used_exclusions: HashMap::new(),
59 internal_access: HashSet::new(),
60 excluded_foundation_libraries: HashSet::new(),
61 excluded_foundation: HashSet::new(),
62 exposed_foundation: None,
63 native_flavor: None,
64 native_imports: Vec::new(),
65 native_flavor_imports: Vec::new(),
66 role: "standard".into(),
67 blank: false,
68 }
69 }
70
71 pub fn configure(clauses: &[Form]) -> Result<Self, String> {
72 Self::configure_with(clauses, known_namespace)
73 }
74
75 pub fn configure_with(
76 clauses: &[Form],
77 available: impl Fn(&str) -> bool,
78 ) -> Result<Self, String> {
79 let mut excluded = HashSet::new();
80 let mut overrides = HashMap::new();
81 let mut requires = Vec::new();
82 let mut uses = Vec::new();
83 let mut excluded_foundation = HashSet::new();
84 let mut exposed_foundation = None;
85 let mut override_seen = false;
86 let mut blank = false;
87 let mut config_seen = false;
88 let mut native_flavor = None;
89 let mut native_flavor_imports = Vec::new();
90 let mut native_imports = Vec::new();
91 let mut role = "standard".to_owned();
92 let mut global_alias = None;
93 let mut declared_global_imports = Vec::new();
94
95 for clause in clauses {
96 let values = list(clause, "ns clauses must be non-empty lists")?;
97 let head = values.first().ok_or("ns clauses must be non-empty lists")?;
98 let name = keyword(head, "ns clause must start with a keyword")?;
99 match name {
100 "config" => {
101 if config_seen {
102 return Err("ns accepts only one :config clause".into());
103 }
104 config_seen = true;
105 if values.len() != 2 {
106 return Err(":config expects one map".into());
107 }
108 parse_config(
109 &values[1],
110 &mut blank,
111 &mut excluded_foundation,
112 &mut exposed_foundation,
113 &mut override_seen,
114 &mut excluded,
115 &mut overrides,
116 &mut role,
117 &mut global_alias,
118 &mut declared_global_imports,
119 )?;
120 }
121 "require" => requires.extend(values[1..].iter().cloned()),
122 "use" => uses.extend(values[1..].iter().cloned()),
123 "flavor" => {
124 if native_flavor.is_some() {
125 return Err("ns accepts only one :flavor clause".into());
126 }
127 let (flavor, imports) = parse_native_flavor(values)?;
128 native_flavor = Some(flavor);
129 native_flavor_imports = imports;
130 }
131 "import" => parse_native_imports(&values[1..], &mut native_imports)?,
132 other => return Err(format!("Unsupported ns clause: :{other}")),
133 }
134 }
135
136 if blank && override_seen {
137 return Err(":config :blank true cannot be combined with :override".into());
138 }
139 if blank && exposed_foundation.is_some() {
140 return Err(":config :blank true cannot be combined with :only".into());
141 }
142 if override_seen && exposed_foundation.is_some() {
143 return Err(":config :override cannot be combined with :only".into());
144 }
145
146 for library in overrides.keys() {
147 if excluded.contains(library) {
148 return Err(format!(
149 "Foundation library cannot be both excluded and aliased: {library}"
150 ));
151 }
152 }
153
154 let mut config = Self::default();
155 config.excluded_foundation_libraries = excluded.clone();
156 config.excluded_foundation = excluded_foundation;
157 config.exposed_foundation = exposed_foundation;
158 config.global_alias = global_alias;
159 config.declared_global_imports = declared_global_imports;
160 config.native_flavor = native_flavor;
161 config.native_imports = native_imports;
162 config.native_flavor_imports = native_flavor_imports;
163 config.role = role;
164 config.blank = blank;
165 for (library, namespace, _) in FOUNDATION_LIBRARIES {
166 if excluded.contains(*library) {
167 continue;
168 }
169 if let Some(alias) = overrides.get(*library) {
170 config.put_alias(alias, namespace)?;
171 }
172 }
173 for require in requires {
174 config.apply_require(&require, &available)?;
175 }
176 for use_form in uses {
177 config.apply_use(&use_form, &available)?;
178 }
179 Ok(config)
180 }
181
182 pub fn required_namespaces(&self) -> &[String] {
183 &self.required_namespaces
184 }
185
186 pub fn lazy_target(&self, alias: &str) -> Option<&str> {
187 self.lazy_aliases.get(alias).map(String::as_str)
188 }
189
190 pub fn used_namespaces(&self) -> &[String] {
191 &self.used_namespaces
192 }
193
194 pub fn used_symbol_excluded(&self, namespace: &str, symbol: &str) -> bool {
195 self.used_exclusions
196 .get(namespace)
197 .is_some_and(|excluded| excluded.contains(symbol))
198 }
199
200 pub fn internal_access(&self) -> &HashSet<String> {
201 &self.internal_access
202 }
203
204 pub fn excluded_foundation(&self) -> &HashSet<String> {
205 &self.excluded_foundation
206 }
207
208 pub fn excluded_foundation_libraries(&self) -> &HashSet<String> {
209 &self.excluded_foundation_libraries
210 }
211
212 pub fn exposed_foundation(&self) -> Option<&HashSet<String>> {
213 self.exposed_foundation.as_ref()
214 }
215
216 pub fn blank(&self) -> bool {
217 self.blank
218 }
219
220 pub fn native_flavor(&self) -> Option<&str> {
221 self.native_flavor.as_deref()
222 }
223
224 pub fn native_imports(&self) -> &[(String, String)] {
225 &self.native_imports
226 }
227
228 pub fn native_flavor_imports(&self) -> &[(String, String)] {
229 &self.native_flavor_imports
230 }
231
232 pub fn role(&self) -> &str {
233 &self.role
234 }
235
236 pub fn aliases(&self) -> Vec<(String, String)> {
237 self.aliases
238 .iter()
239 .map(|(alias, namespace)| (alias.clone(), namespace.clone()))
240 .collect()
241 }
242
243 pub fn global_alias(&self) -> Option<&str> {
244 self.global_alias.as_deref()
245 }
246
247 pub fn declared_global_imports(&self) -> &[String] {
248 &self.declared_global_imports
249 }
250
251 pub fn set_global_aliases(&mut self, aliases: impl IntoIterator<Item = (String, String)>) {
252 self.global_aliases = aliases.into_iter().collect();
253 }
254
255 fn put_alias(&mut self, alias: &str, namespace: &str) -> Result<(), String> {
256 if alias.is_empty() {
257 return Err("Namespace alias cannot be empty".into());
258 }
259 if alias == "-" {
260 return Err("Namespace alias is reserved: -".into());
261 }
262 if let Some(native_namespace) = crate::core::canonical_native_symbol(alias) {
263 return Err(format!(
264 "Namespace alias already refers to {native_namespace}: {alias}"
265 ));
266 }
267 if let Some(previous) = self.aliases.get(alias) {
268 if previous != namespace {
269 return Err(format!(
270 "Namespace alias already refers to {previous}: {alias}"
271 ));
272 }
273 return Ok(());
274 }
275 self.aliases.insert(alias.into(), namespace.into());
276 Ok(())
277 }
278
279 pub fn apply_require(
280 &mut self,
281 form: &Form,
282 available: &impl Fn(&str) -> bool,
283 ) -> Result<(), String> {
284 let (target, options) = match form {
285 Form::Vector(items) => {
286 let target = match items.first() {
287 Some(Form::Symbol(target)) => target.as_str(),
288 _ => return Err(":require namespace must be a symbol".into()),
289 };
290 (normalize_namespace(target), &items[1..])
291 }
292 Form::List(items)
293 if items.len() == 2
294 && matches!(&items[0], Form::Symbol(q) if q == "quote")
295 && matches!(&items[1], Form::Symbol(_)) =>
296 {
297 let target = match &items[1] {
298 Form::Symbol(target) => target.as_str(),
299 _ => unreachable!(),
300 };
301 (normalize_namespace(target), &[][..])
302 }
303 _ => return Err(":require expects vectors such as [hara.lib.string :as str]".into()),
304 };
305 if !known_namespace(target) && !available(target) {
306 return Err(format!(
307 "Cannot require missing generated namespace: {target}"
308 ));
309 }
310 if options.len() % 2 != 0 {
311 return Err(format!("Malformed :require options for {target}"));
312 }
313 let lazy = options.chunks(2).any(|option| {
314 matches!(&option[0], Form::Keyword(name) if name == "lazy")
315 && matches!(&option[1], Form::Bool(true))
316 });
317 let has_alias = options
318 .chunks(2)
319 .any(|option| matches!(&option[0], Form::Keyword(name) if name == "as"));
320 if lazy && !has_alias {
321 return Err(":require :lazy requires :as".into());
322 }
323 if !lazy && !self.required_namespaces.iter().any(|value| value == target) {
324 self.required_namespaces.push(target.into());
325 }
326 for option in options.chunks(2) {
327 let name = keyword(&option[0], "Malformed :require options")?;
328 match name {
329 "as" => {
330 let alias = symbol(&option[1], ":require :as expects an unqualified symbol")?;
331 if alias.contains('/') {
332 return Err(":require :as expects an unqualified symbol".into());
333 }
334 self.put_alias(alias, target)?;
335 if lazy {
336 self.lazy_aliases.insert(alias.into(), target.into());
337 }
338 }
339 "refer" => {
340 if lazy {
341 return Err(":require :lazy cannot be combined with :refer".into());
342 }
343 if matches!(&option[1], Form::Keyword(name) if name == "all") {
344 if !self.used_namespaces.iter().any(|value| value == target) {
345 self.used_namespaces.push(target.into());
346 }
347 continue;
348 }
349 let names = vector(
350 &option[1],
351 ":require :refer expects a vector of symbols or :all",
352 )?;
353 for value in names {
354 let name = symbol(value, ":require :refer expects unqualified symbols")?;
355 if qualified_symbol(name) {
356 return Err(":require :refer expects unqualified symbols".into());
357 }
358 let canonical = canonical(target, name);
359 if let Some(previous) = self.refers.insert(name.into(), canonical) {
360 return Err(format!(
361 "Referred symbol already exists: {name} ({previous})"
362 ));
363 }
364 }
365 }
366 "refer-macros" => {
367 if lazy {
368 return Err(":require :lazy cannot be combined with :refer-macros".into());
369 }
370 let names = vector(
371 &option[1],
372 ":require :refer-macros expects a vector of symbols",
373 )?;
374 for value in names {
375 let name =
376 symbol(value, ":require :refer-macros expects unqualified symbols")?;
377 if qualified_symbol(name) {
378 return Err(":require :refer-macros expects unqualified symbols".into());
379 }
380 let canonical = canonical(target, name);
381 if let Some(previous) =
382 self.macro_refers.insert(name.into(), canonical.clone())
383 {
384 if previous != canonical {
385 return Err(format!(
386 "Referred macro already exists: {name} ({previous})"
387 ));
388 }
389 }
390 }
391 }
392 "lazy" => {
393 if !matches!(&option[1], Form::Bool(true)) {
394 return Err(":require :lazy expects true".into());
395 }
396 }
397 "reload" => {
398 if !matches!(&option[1], Form::Bool(true)) {
399 return Err(":require :reload expects true".into());
400 }
401 }
402 "access" => {
403 if !matches!(&option[1], Form::Bool(true)) {
404 return Err(":require :access expects true".into());
405 }
406 self.internal_access.insert(target.into());
407 }
408 "exclude" => {
409 let names =
410 vector(&option[1], ":require :exclude expects a vector of symbols")?;
411 for value in names {
412 let name = symbol(value, ":require :exclude expects unqualified symbols")?;
413 if qualified_symbol(name) {
414 return Err(":require :exclude expects unqualified symbols".into());
415 }
416 self.used_exclusions
417 .entry(target.into())
418 .or_default()
419 .insert(name.into());
420 if target == "std.foundation" {
421 self.excluded_foundation.insert(name.into());
422 }
423 }
424 }
425 other => return Err(format!("Unsupported :require option: :{other}")),
426 }
427 }
428 Ok(())
429 }
430
431 pub fn apply_use(
432 &mut self,
433 form: &Form,
434 available: &impl Fn(&str) -> bool,
435 ) -> Result<(), String> {
436 let target = match form {
437 Form::Symbol(target) if !target.contains('/') => normalize_namespace(target),
438 _ => return Err(":use expects unqualified namespace symbols".into()),
439 };
440 if !known_namespace(target) && !available(target) {
441 return Err(format!("Cannot use missing generated namespace: {target}"));
442 }
443 if !self.required_namespaces.iter().any(|value| value == target) {
444 self.required_namespaces.push(target.into());
445 }
446 if !self.used_namespaces.iter().any(|value| value == target) {
447 self.used_namespaces.push(target.into());
448 }
449 Ok(())
450 }
451}
452
453fn parse_native_flavor(values: &[Form]) -> Result<(String, Vec<(String, String)>), String> {
454 let flavor = match values.get(1) {
455 Some(Form::Keyword(flavor)) if !flavor.contains('/') && flavor != "wasm" => flavor,
456 Some(Form::Keyword(flavor)) if flavor == "wasm" => {
457 return Err("native/unsupported-flavor: :wasm (Wasm modules use :import)".into())
458 }
459 Some(Form::Keyword(flavor)) => return Err(format!("native/invalid-flavor: :{flavor}")),
460 _ => return Err(":flavor expects an unqualified host keyword".into()),
461 };
462 Err(format!(
463 "native/unsupported-flavor: :{flavor} (host flavors are only available on JVM/.NET runtimes)"
464 ))
465}
466
467fn parse_native_imports(
468 specifications: &[Form],
469 imports: &mut Vec<(String, String)>,
470) -> Result<(), String> {
471 for specification in specifications {
472 match specification {
473 Form::Symbol(module) if !module.contains('/') => {
474 imports.push((module.clone(), module.clone()));
475 }
476 Form::Vector(values) if !values.is_empty() => {
477 let package = match &values[0] {
478 Form::Symbol(package) if !package.contains('/') => package,
479 _ => return Err(":import package must be a symbol".into()),
480 };
481 if values.len() == 1 {
482 return Err(":import package vector requires at least one module".into());
483 }
484 for module in &values[1..] {
485 let module = match module {
486 Form::Symbol(module) if !module.contains('/') && !module.contains('.') => {
487 module
488 }
489 _ => return Err(":import module must be an unqualified symbol".into()),
490 };
491 imports.push((module.clone(), format!("{package}.{module}")));
492 }
493 }
494 _ => return Err(":import expects module symbols or package vectors".into()),
495 }
496 }
497 Ok(())
498}
499
500fn parse_config(
501 form: &Form,
502 blank: &mut bool,
503 foundation_overrides: &mut HashSet<String>,
504 foundation_exposure: &mut Option<HashSet<String>>,
505 override_seen: &mut bool,
506 excluded: &mut HashSet<String>,
507 overrides: &mut HashMap<String, String>,
508 role: &mut String,
509 global_alias: &mut Option<String>,
510 declared_global_imports: &mut Vec<String>,
511) -> Result<(), String> {
512 let options = match form {
513 Form::Map(options) => options,
514 _ => return Err(":config expects one map".into()),
515 };
516 for (key, value) in options {
517 match keyword(key, ":config keys must be unqualified keywords")? {
518 "blank" => {
519 *blank = match value {
520 Form::Bool(value) => *value,
521 _ => return Err(":config :blank expects a boolean".into()),
522 };
523 }
524 "override" => {
525 *override_seen = true;
526 for item in vector(
527 value,
528 ":config :override expects a vector of unqualified symbols",
529 )? {
530 let name = symbol(
531 item,
532 ":config :override expects a vector of unqualified symbols",
533 )?;
534 if qualified_symbol(name) {
535 return Err(
536 ":config :override expects a vector of unqualified symbols".into()
537 );
538 }
539 if !foundation_overrides.insert(name.into()) {
540 return Err(format!("Duplicate Foundation override: {name}"));
541 }
542 }
543 }
544 "only" => {
545 let mut exposed = HashSet::new();
546 for item in vector(
547 value,
548 ":config :only expects a vector of unqualified symbols",
549 )? {
550 let name = symbol(
551 item,
552 ":config :only expects a vector of unqualified symbols",
553 )?;
554 if qualified_symbol(name) {
555 return Err(
556 ":config :only expects a vector of unqualified symbols".into()
557 );
558 }
559 if !exposed.insert(name.into()) {
560 return Err(format!("Duplicate Foundation selection: {name}"));
561 }
562 }
563 *foundation_exposure = Some(exposed);
564 }
565 "rename" => {
566 parse_rename(value, excluded, overrides)?;
567 }
568 "role" => {
569 let value = keyword(
570 value,
571 ":config :role expects :default, :internal, or :facade",
572 )?;
573 if !matches!(value, "default" | "internal" | "facade") {
574 return Err(":config :role expects :default, :internal, or :facade".into());
575 }
576 *role = if value == "default" {
577 "standard".to_owned()
578 } else {
579 value.to_owned()
580 };
581 }
582 "set-global-alias" => {
583 let value = symbol(
584 value,
585 ":config :set-global-alias expects an unqualified symbol",
586 )?;
587 if qualified_symbol(value) {
588 return Err(":config :set-global-alias expects an unqualified symbol".into());
589 }
590 if value == "-" {
591 return Err(":config :set-global-alias is reserved: -".into());
592 }
593 *global_alias = Some(value.to_owned());
594 }
595 "set-global" => {
596 for item in vector(
597 value,
598 ":config :set-global expects a vector of qualified Vars",
599 )? {
600 let name = symbol(
601 item,
602 ":config :set-global expects a vector of qualified Vars",
603 )?;
604 if !qualified_symbol(name) {
605 return Err(":config :set-global expects qualified Vars".into());
606 }
607 if declared_global_imports.iter().any(|value| value == name) {
608 return Err(format!("Duplicate global import: {name}"));
609 }
610 declared_global_imports.push(name.into());
611 }
612 }
613 other => return Err(format!("Unsupported :config option: :{other}")),
614 }
615 }
616 Ok(())
617}
618
619fn parse_rename(
620 form: &Form,
621 excluded: &mut HashSet<String>,
622 overrides: &mut HashMap<String, String>,
623) -> Result<(), String> {
624 if matches!(form, Form::Keyword(name) if name == "all") {
625 return Ok(());
626 }
627 let options = match form {
628 Form::Map(options) => options,
629 _ => return Err(":rename expects :all or an options map".into()),
630 };
631 for (key, value) in options {
632 match keyword(key, ":rename option keys must be keywords")? {
633 "exclude" => {
634 for item in vector(
635 value,
636 ":rename :exclude expects a vector of library symbols",
637 )? {
638 let library = library(symbol(
639 item,
640 ":rename :exclude expects unqualified library symbols",
641 )?)?;
642 if !excluded.insert(library.into()) {
643 return Err(format!("Duplicate Foundation library exclusion: {library}"));
644 }
645 }
646 }
647 "alias" => {
648 let aliases = match value {
649 Form::Map(aliases) => aliases,
650 _ => return Err(":rename :alias expects a map".into()),
651 };
652 for (library_form, alias_form) in aliases {
653 let library = library(symbol(
654 library_form,
655 ":rename :alias expects library symbols",
656 )?)?;
657 let alias =
658 symbol(alias_form, "Foundation library aliases must be unqualified symbols")?;
659 if alias.contains('/') {
660 return Err("Foundation library aliases must be unqualified symbols".into());
661 }
662 if overrides.insert(library.into(), alias.into()).is_some() {
663 return Err(format!("Duplicate Foundation library alias: {library}"));
664 }
665 }
666 }
667 other => return Err(format!("Unsupported :config :rename option: :{other}")),
668 }
669 }
670 Ok(())
671}
672
673fn list<'a>(form: &'a Form, error: &str) -> Result<&'a [Form], String> {
674 match form {
675 Form::List(values) => Ok(values),
676 _ => Err(error.into()),
677 }
678}
679fn vector<'a>(form: &'a Form, error: &str) -> Result<&'a [Form], String> {
680 match form {
681 Form::Vector(values) => Ok(values),
682 _ => Err(error.into()),
683 }
684}
685fn keyword<'a>(form: &'a Form, error: &str) -> Result<&'a str, String> {
686 match form {
687 Form::Keyword(value) => Ok(value),
688 _ => Err(error.into()),
689 }
690}
691fn symbol<'a>(form: &'a Form, error: &str) -> Result<&'a str, String> {
692 match form {
693 Form::Symbol(value) => Ok(value),
694 _ => Err(error.into()),
695 }
696}
697fn qualified_symbol(value: &str) -> bool {
698 value != "/" && value.contains('/')
699}
700fn library(value: &str) -> Result<&str, String> {
701 if value.contains('/') {
702 return Err("Foundation library names must be unqualified symbols".into());
703 }
704 FOUNDATION_LIBRARIES
705 .iter()
706 .find(|(library, _, _)| *library == value)
707 .map(|(library, _, _)| *library)
708 .ok_or_else(|| format!("Unknown Foundation library: {value}"))
709}
710pub(crate) fn normalize_namespace(value: &str) -> &str {
711 match value {
712 "core" | "hara.lib.core" => "std.foundation",
713 "hara.lib.string" => "std.foundation.string",
714 "hara.lib.promise" => "std.foundation.promise",
715 "hara.lib.bytes" => "std.foundation.bytes",
716 "hara.lib.socket" => "std.native.Socket",
717 "hara.lib.file" => "std.native.File",
718 value => value,
719 }
720}
721pub(crate) fn known_namespace(value: &str) -> bool {
722 let value = normalize_namespace(value);
723 value == "std.foundation"
724 || value == "std.foundation.coroutine"
725 || value == "std.native"
726 || value.starts_with("std.native.")
727 || FOUNDATION_LIBRARIES
728 .iter()
729 .any(|(_, namespace, _)| *namespace == value)
730}
731fn canonical(namespace: &str, method: &str) -> String {
732 let namespace = normalize_namespace(namespace);
733 if namespace.starts_with("std.native.") {
734 return format!("{namespace}/{method}");
735 }
736 if namespace == "std.foundation" {
737 return format!("std.foundation/{method}");
738 }
739 if namespace == "std.foundation.coroutine" {
744 return format!("std.foundation.coroutine/{method}");
745 }
746 if FOUNDATION_LIBRARIES
747 .iter()
748 .any(|(_, library_namespace, _)| *library_namespace == namespace)
749 {
750 return format!("{namespace}/{method}");
751 }
752 match (namespace, method) {
753 ("std.foundation", method) => method.into(),
754 ("std.lib.string", method) => format!("str/{method}"),
755 ("std.lib.promise", "then") => "promise/then".into(),
756 ("std.lib.promise", "catch") => "promise/catch".into(),
757 ("std.lib.promise", method) => format!("promise/{method}"),
758 ("std.lib.bytes", method) => format!("bytes/{method}"),
759 ("std.lib.socket", method) => format!("socket/{method}"),
760 ("std.lib.file", method) => format!("file/{method}"),
761 (namespace, method) => format!("{namespace}/{method}"),
762 }
763}
764
765#[cfg(test)]
766#[path = "generated/tests.rs"]
767mod tests;