use super::{
OptionsFieldBridges, SHADOWABLE_BUILTIN_TYPES, constructor_param_type, constructor_rust_type_to_python,
is_python_builtin_name, pyi_docstring, python_safe_name, qualify_parameter_type, qualify_shadowed_builtin_types,
substitute_capsule_type,
};
use crate::backends::pyo3::type_map::python_type;
use crate::codegen::shared::binding_fields;
use crate::core::config::workspace::ClientConstructorConfig;
use crate::core::config::{Language, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
fn shadowed_builtins(typ: &TypeDef, config: &ResolvedCrateConfig) -> Vec<&'static str> {
let field_names: std::collections::HashSet<String> = binding_fields(&typ.fields)
.map(|field| {
config
.resolve_field_name(Language::Python, &typ.name, &field.name)
.unwrap_or_else(|| field.name.clone())
})
.collect();
SHADOWABLE_BUILTIN_TYPES
.iter()
.copied()
.filter(|builtin| {
field_names.contains(*builtin)
|| typ.methods.iter().any(|method| {
(method.is_static || !field_names.contains(&method.name))
&& python_safe_name(&method.name) == *builtin
})
})
.collect()
}
fn opaque_shadowed_builtins(typ: &TypeDef) -> Vec<&'static str> {
SHADOWABLE_BUILTIN_TYPES
.iter()
.copied()
.filter(|builtin| {
typ.methods
.iter()
.any(|method| python_safe_name(&method.name) == *builtin)
})
.collect()
}
pub(super) fn gen_opaque_type_stub(
typ: &TypeDef,
capsule_names: &std::collections::HashSet<&str>,
streaming_return_types: &std::collections::HashMap<(Option<String>, String), String>,
ctor: Option<&ClientConstructorConfig>,
) -> String {
let mut lines = vec![];
let shadowed = opaque_shadowed_builtins(typ);
lines.push(format!("class {}:", typ.name));
if let Some(ctor) = ctor {
let mut params: Vec<String> = ctor
.params
.iter()
.map(|p| {
let py_type = constructor_rust_type_to_python(&p.ty);
let py_type = qualify_parameter_type(&p.name, py_type);
format!("{}: {}", p.name, py_type)
})
.collect();
let single = format!(" def __init__(self, {}) -> None: ...", params.join(", "));
if single.len() <= 100 {
lines.push(single);
} else {
let mut wrapped = String::from(" def __init__(\n self,\n");
for param in &mut params {
wrapped.push_str(&crate::backends::pyo3::template_env::render(
"stub_wrapped_param_line.jinja",
crate::alef_context! { param => param },
));
}
wrapped.push_str(" ) -> None: ...");
lines.push(wrapped);
}
}
for method in &typ.methods {
if !method.is_static {
lines.push(gen_method_stub(
method,
false,
capsule_names,
Some(&typ.name),
streaming_return_types,
&shadowed,
));
}
}
for method in &typ.methods {
if method.is_static {
lines.push(gen_method_stub(
method,
true,
capsule_names,
Some(&typ.name),
streaming_return_types,
&shadowed,
));
}
}
if typ.methods.is_empty() && ctor.is_none() {
return format!("class {}: ...", typ.name);
}
lines.join("\n")
}
pub(super) fn gen_type_stub(
typ: &TypeDef,
api: &ApiSurface,
config: &ResolvedCrateConfig,
capsule_names: &std::collections::HashSet<&str>,
options_field_bridges: &OptionsFieldBridges<'_>,
emit_docstrings: bool,
streaming_return_types: &std::collections::HashMap<(Option<String>, String), String>,
) -> String {
let mut lines = vec![];
lines.push(format!("class {}:", typ.name));
if emit_docstrings && let Some(docstring) = pyi_docstring(&typ.doc, " ") {
lines.push(docstring);
}
let shadowed = shadowed_builtins(typ, config);
let mut emitted_attribute_names: std::collections::HashSet<String> = std::collections::HashSet::new();
for field in binding_fields(&typ.fields) {
let type_str = if let Some((_, type_alias, trait_name)) = options_field_bridges.get(typ.name.as_str()) {
if let Some(alias) = type_alias {
if field.name == *alias {
trait_name.or(*type_alias).unwrap_or("object").to_string()
} else {
python_type(&field.ty)
}
} else {
python_type(&field.ty)
}
} else {
python_type(&field.ty)
};
let is_optional_duration = typ.has_default && matches!(field.ty, TypeRef::Duration) && !field.optional;
let field_type = if (is_optional_duration || field.optional) && !type_str.contains("| None") {
format!("{} | None", type_str)
} else {
type_str
};
let field_type = qualify_shadowed_builtin_types(&field_type, &shadowed);
let stub_field_name = config
.resolve_field_name(Language::Python, &typ.name, &field.name)
.unwrap_or_else(|| field.name.clone());
emitted_attribute_names.insert(stub_field_name.clone());
lines.push(format!(" {stub_field_name}: {field_type}"));
if emit_docstrings && let Some(docstring) = pyi_docstring(&field.doc, " ") {
lines.push(docstring);
}
}
lines.push(gen_type_init_stub(typ, api, config, options_field_bridges));
for method in &typ.methods {
if !method.is_static {
if emitted_attribute_names.contains(&method.name) {
continue;
}
lines.push(gen_method_stub(
method,
false,
capsule_names,
Some(&typ.name),
streaming_return_types,
&shadowed,
));
}
}
for method in &typ.methods {
if method.is_static {
lines.push(gen_method_stub(
method,
true,
capsule_names,
Some(&typ.name),
streaming_return_types,
&shadowed,
));
}
}
lines.join("\n")
}
fn gen_type_init_stub(
typ: &TypeDef,
api: &ApiSurface,
config: &ResolvedCrateConfig,
options_field_bridges: &OptionsFieldBridges<'_>,
) -> String {
let bridge_field_name = options_field_bridges.get(typ.name.as_str()).map(|(kwarg, _, _)| *kwarg);
let mut fields: Vec<_> = binding_fields(&typ.fields)
.filter(|f| f.cfg.as_deref().is_none_or(cfg_present_for_pyo3_stub))
.filter(|f| bridge_field_name != Some(f.name.as_str()))
.collect();
fields.sort_by_key(|f| f.optional as u8);
let mut bare_defaulted = vec![false; fields.len()];
let mut suffix_defaulted = true;
for idx in (0..fields.len()).rev() {
let field = fields[idx];
let eligible_bare_default = !typ.has_default && field.has_bare_serde_enum_default() && suffix_defaulted;
bare_defaulted[idx] = eligible_bare_default;
suffix_defaulted &= typ.has_default || field.optional || eligible_bare_default;
}
let py_field_renames: std::collections::HashMap<String, String> = typ
.fields
.iter()
.filter_map(|f| {
config
.resolve_field_name(Language::Python, &typ.name, &f.name)
.map(|renamed| (f.name.clone(), renamed))
})
.collect();
let renames_ref = if py_field_renames.is_empty() {
None
} else {
Some(&py_field_renames)
};
let shadowed = shadowed_builtins(typ, config);
let mut params: Vec<String> = fields
.iter()
.enumerate()
.map(|(idx, f)| {
let type_str = qualify_shadowed_builtin_types(&constructor_param_type(&f.ty), &shadowed);
let accepts_none = f.optional
|| matches!(f.ty, TypeRef::Optional(_) | TypeRef::Duration)
|| crate::backends::pyo3::gen_bindings::constructors::should_option_for_nested_default(typ, f, api);
let param_type = if accepts_none && !type_str.ends_with("| None") {
format!("{} | None", type_str)
} else {
type_str
};
let param_name = crate::backends::pyo3::gen_bindings::constructors::resolve_param_ident(
&f.name,
f.serde_rename.as_ref(),
renames_ref,
);
let param_name = param_name.strip_prefix("r#").map(str::to_owned).unwrap_or(param_name);
let is_optional_duration = matches!(f.ty, TypeRef::Duration) && !f.optional;
if typ.has_default || f.optional || is_optional_duration || bare_defaulted[idx] {
let default = if accepts_none { "None" } else { "..." };
format!("{param_name}: {param_type} = {default}")
} else {
format!("{param_name}: {param_type}")
}
})
.collect();
if let Some((kwarg_name, type_alias, trait_name)) = options_field_bridges.get(typ.name.as_str()) {
let visitor_type = trait_name.or(*type_alias).unwrap_or("object");
params.push(format!("{kwarg_name}: {visitor_type} | object | None = None"));
}
let has_builtin_param = params
.iter()
.any(|p| is_python_builtin_name(p.split(':').next().unwrap_or("").trim()));
let single_line = format!(" def __init__(self, {}) -> None: ...", params.join(", "));
if single_line.len() <= 100 && !has_builtin_param {
single_line
} else {
let mut wrapped = String::from(" def __init__(\n");
wrapped.push_str(" self,\n");
for param in ¶ms {
let name = param.split(':').next().unwrap_or("").trim();
if is_python_builtin_name(name) {
wrapped.push_str(&crate::backends::pyo3::template_env::render(
"stub_param_wrapped_noqa.jinja",
crate::alef_context! { param => param, indent => " " },
));
} else {
wrapped.push_str(&crate::backends::pyo3::template_env::render(
"stub_param_wrapped.jinja",
crate::alef_context! { param => param, indent => " " },
));
}
}
wrapped.push_str(" ) -> None: ...");
wrapped
}
}
fn cfg_present_for_pyo3_stub(cfg: &str) -> bool {
let normalized: String = cfg.chars().filter(|c| !c.is_whitespace()).collect();
if normalized == "not(target_arch=\"wasm32\")" {
return true;
}
if normalized.starts_with("feature=") {
return true;
}
if normalized.starts_with("any(") && normalized.ends_with(')') {
let inner = &normalized[4..normalized.len() - 1];
return inner
.split(',')
.all(|part| part.starts_with("feature=") || part == "not(target_arch=\"wasm32\")");
}
false
}
fn gen_method_stub(
method: &MethodDef,
is_static: bool,
capsule_names: &std::collections::HashSet<&str>,
owner_type: Option<&str>,
streaming_return_types: &std::collections::HashMap<(Option<String>, String), String>,
owner_shadowed_builtins: &[&str],
) -> String {
let (required, optional): (Vec<_>, Vec<_>) = method.params.iter().partition(|p| !p.optional);
let mut params: Vec<String> = required
.iter()
.map(|p| {
let param_type = substitute_capsule_type(&python_type(&p.ty), capsule_names);
let param_type = qualify_parameter_type(&p.name, ¶m_type);
let param_type = qualify_shadowed_builtin_types(¶m_type, owner_shadowed_builtins);
format!("{}: {}", p.name, param_type)
})
.collect();
params.extend(optional.iter().map(|p| {
let type_str = substitute_capsule_type(&python_type(&p.ty), capsule_names);
let type_str = qualify_parameter_type(&p.name, &type_str);
let type_str = qualify_shadowed_builtin_types(&type_str, owner_shadowed_builtins);
let param_type = if !type_str.ends_with("| None") {
format!("{} | None", type_str)
} else {
type_str
};
format!("{}: {} = None", p.name, param_type)
}));
let streaming_key = (owner_type.map(str::to_string), method.name.clone());
let is_streaming = streaming_return_types.contains_key(&streaming_key);
let return_type = if let Some(item_type) = streaming_return_types.get(&streaming_key) {
format!("AsyncIterator[{item_type}]")
} else {
substitute_capsule_type(&python_type(&method.return_type), capsule_names)
};
let return_type = qualify_shadowed_builtin_types(&return_type, owner_shadowed_builtins);
let indent = " ";
let safe_name = python_safe_name(&method.name);
let def_kw = if method.is_async && !is_streaming {
"async def"
} else {
"def"
};
let has_builtin_param = params
.iter()
.any(|p| is_python_builtin_name(p.split(':').next().unwrap_or("").trim()));
let emit_params_wrapped = |prefix: &str, suffix: &str| -> String {
let mut wrapped = format!("{prefix}\n");
for param in ¶ms {
let name = param.split(':').next().unwrap_or("").trim();
if is_python_builtin_name(name) {
wrapped.push_str(&crate::backends::pyo3::template_env::render(
"stub_param_method_wrapped_noqa.jinja",
crate::alef_context! { indent => indent, param => param },
));
} else {
wrapped.push_str(&crate::backends::pyo3::template_env::render(
"stub_param_method_wrapped.jinja",
crate::alef_context! { indent => indent, param => param },
));
}
}
wrapped.push_str(suffix);
wrapped
};
if is_static {
if params.is_empty() {
format!(
"{}@staticmethod\n{}{} {}() -> {}: ...",
indent, indent, def_kw, safe_name, return_type
)
} else {
let prefix = format!("{}@staticmethod\n{}{} {}(", indent, indent, def_kw, safe_name);
let suffix = format!("{}) -> {}: ...", indent, return_type);
let def_line = format!(
"{}{} {}({}) -> {}: ...",
indent,
def_kw,
safe_name,
params.join(", "),
return_type
);
if def_line.len() <= 100 && !has_builtin_param {
format!(
"{}@staticmethod\n{}{} {}({}) -> {}: ...",
indent,
indent,
def_kw,
safe_name,
params.join(", "),
return_type
)
} else {
emit_params_wrapped(&prefix, &suffix)
}
}
} else if params.is_empty() {
format!("{}{} {}(self) -> {}: ...", indent, def_kw, safe_name, return_type)
} else {
let single_line = format!(
"{}{} {}(self, {}) -> {}: ...",
indent,
def_kw,
safe_name,
params.join(", "),
return_type
);
if single_line.len() <= 100 && !has_builtin_param {
single_line
} else {
let prefix = format!("{}{} {}(\n{} self,", indent, def_kw, safe_name, indent);
let suffix = format!("{}) -> {}: ...", indent, return_type);
emit_params_wrapped(&prefix, &suffix)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::PrimitiveType;
use crate::core::ir::{FieldDef, ParamDef};
fn streaming_method(name: &str) -> crate::core::ir::MethodDef {
crate::core::ir::MethodDef {
name: name.to_string(),
params: Vec::new(),
return_type: TypeRef::Named("Ignored".to_string()),
is_async: true,
is_static: false,
error_type: None,
doc: String::new(),
receiver: Some(crate::core::ir::ReceiverKind::Ref),
cfg: None,
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}
}
#[test]
fn streaming_method_stub_keeps_a_plain_def_keyword_despite_method_is_async() {
let method = streaming_method("chat_stream");
let mut streaming_return_types = std::collections::HashMap::new();
streaming_return_types.insert(
(Some("DefaultClient".to_string()), "chat_stream".to_string()),
"ChatCompletionChunk".to_string(),
);
let stub = gen_method_stub(
&method,
false,
&std::collections::HashSet::new(),
Some("DefaultClient"),
&streaming_return_types,
&[],
);
assert!(
stub.trim_start().starts_with("def chat_stream("),
"a streaming method's call is synchronous (it returns the AsyncIterator directly); \
the stub must keep a plain `def`, not `async def`, got:\n{stub}"
);
assert!(
stub.contains("-> AsyncIterator[ChatCompletionChunk]"),
"the streaming return type must still be AsyncIterator[Item]:\n{stub}"
);
}
#[test]
fn a_non_streaming_async_method_stub_keeps_async_def() {
let method = streaming_method("chat");
let stub = gen_method_stub(
&method,
false,
&std::collections::HashSet::new(),
Some("DefaultClient"),
&std::collections::HashMap::new(),
&[],
);
assert!(
stub.trim_start().starts_with("async def chat("),
"a non-streaming async method must keep `async def`, got:\n{stub}"
);
}
#[test]
fn method_parameter_named_bytes_qualifies_its_builtin_annotation() {
let method = MethodDef {
name: "from_bytes".to_string(),
params: vec![ParamDef {
name: "bytes".to_string(),
ty: TypeRef::Bytes,
..Default::default()
}],
return_type: TypeRef::Named("Document".to_string()),
is_static: true,
..Default::default()
};
let stub = gen_method_stub(
&method,
true,
&std::collections::HashSet::new(),
Some("Document"),
&std::collections::HashMap::new(),
&[],
);
assert!(
stub.contains("bytes: builtins.bytes"),
"the parameter must not resolve its annotation to its own value:\n{stub}"
);
assert!(
!stub.contains("bytes: bytes"),
"the ambiguous annotation is rejected by pyrefly:\n{stub}"
);
}
#[test]
fn class_field_named_bytes_qualifies_all_method_annotations_in_class_scope() {
let method = MethodDef {
name: "consume".to_string(),
params: vec![ParamDef {
name: "payload".to_string(),
ty: TypeRef::Bytes,
..Default::default()
}],
return_type: TypeRef::Bytes,
..Default::default()
};
let with_shadow = TypeDef {
name: "Container".to_string(),
fields: vec![FieldDef {
name: "bytes".to_string(),
ty: TypeRef::Bytes,
..Default::default()
}],
methods: vec![method.clone()],
..Default::default()
};
let without_shadow = TypeDef {
name: "Container".to_string(),
methods: vec![method],
..Default::default()
};
let render = |typ: &TypeDef| {
gen_type_stub(
typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&std::collections::HashSet::new(),
&OptionsFieldBridges::default(),
false,
&std::collections::HashMap::new(),
)
};
let shadowed = render(&with_shadow);
assert!(
shadowed.contains("def consume(self, payload: builtins.bytes) -> builtins.bytes"),
"class attributes shadow builtin names throughout the class body:\n{shadowed}"
);
let control = render(&without_shadow);
assert!(
control.contains("def consume(self, payload: bytes) -> bytes"),
"annotations should stay concise when no class attribute shadows them:\n{control}"
);
}
fn builtin_named_method_fixture() -> TypeDef {
TypeDef {
name: "Container".to_string(),
methods: vec![
MethodDef {
name: "bytes".to_string(),
return_type: TypeRef::Unit,
..Default::default()
},
MethodDef {
name: "consume".to_string(),
params: vec![ParamDef {
name: "payload".to_string(),
ty: TypeRef::Bytes,
..Default::default()
}],
return_type: TypeRef::Bytes,
..Default::default()
},
],
..Default::default()
}
}
#[test]
fn method_named_bytes_qualifies_sibling_annotations_for_regular_classes() {
let render = |typ: &TypeDef| {
gen_type_stub(
typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&std::collections::HashSet::new(),
&OptionsFieldBridges::default(),
false,
&std::collections::HashMap::new(),
)
};
let mut typ = builtin_named_method_fixture();
let stub = render(&typ);
assert!(
stub.contains("def consume(self, payload: builtins.bytes) -> builtins.bytes"),
"an emitted method name shadows builtins throughout the class scope:\n{stub}"
);
typ.methods.remove(0);
let control = render(&typ);
assert!(
control.contains("def consume(self, payload: bytes) -> bytes"),
"annotations should stay concise without a builtin-named class member:\n{control}"
);
}
#[test]
fn method_named_bytes_qualifies_sibling_annotations_for_opaque_classes() {
let mut typ = builtin_named_method_fixture();
typ.is_opaque = true;
let stub = gen_opaque_type_stub(
&typ,
&std::collections::HashSet::new(),
&std::collections::HashMap::new(),
None,
);
assert!(
stub.contains("def consume(self, payload: builtins.bytes) -> builtins.bytes"),
"opaque class methods share the same class annotation scope:\n{stub}"
);
}
fn object_named_method_fixture() -> TypeDef {
TypeDef {
name: "ObjectContainer".to_string(),
methods: vec![
MethodDef {
name: "object".to_string(),
return_type: TypeRef::Unit,
..Default::default()
},
MethodDef {
name: "consume".to_string(),
params: vec![ParamDef {
name: "payload".to_string(),
ty: TypeRef::Named("object".to_string()),
..Default::default()
}],
return_type: TypeRef::Named("object".to_string()),
..Default::default()
},
],
..Default::default()
}
}
#[test]
fn method_named_object_qualifies_regular_and_opaque_class_annotations() {
let typ = object_named_method_fixture();
let regular = gen_type_stub(
&typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&std::collections::HashSet::new(),
&OptionsFieldBridges::default(),
false,
&std::collections::HashMap::new(),
);
assert!(
regular.contains("def consume(self, payload: builtins.object) -> builtins.object"),
"regular class methods can shadow object annotations:\n{regular}"
);
let opaque = gen_opaque_type_stub(
&typ,
&std::collections::HashSet::new(),
&std::collections::HashMap::new(),
None,
);
assert!(
opaque.contains("def consume(self, payload: builtins.object) -> builtins.object"),
"opaque class methods can shadow object annotations:\n{opaque}"
);
}
#[test]
fn type_init_stub_keeps_pyo3_present_cfg_fields() {
let typ = TypeDef {
name: "UrlExtractionConfig".to_string(),
fields: vec![
FieldDef {
name: "mode".to_string(),
ty: TypeRef::Named("UrlExtractionMode".to_string()),
..Default::default()
},
FieldDef {
name: "crawl".to_string(),
ty: TypeRef::Named("CrawlConfig".to_string()),
cfg: Some("any(feature = \"url-ingestion\", feature = \"url-config-types\")".to_string()),
..Default::default()
},
],
has_default: true,
..Default::default()
};
let api = ApiSurface {
types: vec![TypeDef {
name: "CrawlConfig".to_string(),
..Default::default()
}],
..Default::default()
};
let stub = gen_type_init_stub(
&typ,
&api,
&ResolvedCrateConfig::default(),
&OptionsFieldBridges::default(),
);
assert!(stub.contains("mode: UrlExtractionMode = ..."), "{stub}");
assert!(stub.contains("crawl: CrawlConfig = ..."), "{stub}");
}
#[test]
fn type_init_stub_still_omits_non_pyo3_cfg_fields() {
let typ = TypeDef {
name: "PlatformConfig".to_string(),
fields: vec![FieldDef {
name: "windows_only".to_string(),
ty: TypeRef::Primitive(PrimitiveType::Bool),
cfg: Some("target_os = \"windows\"".to_string()),
..Default::default()
}],
has_default: true,
..Default::default()
};
let stub = gen_type_init_stub(
&typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&OptionsFieldBridges::default(),
);
assert!(!stub.contains("windows_only"), "{stub}");
}
#[test]
fn type_init_stub_keeps_required_sibling_and_defaults_bare_serde_field() {
let typ = TypeDef {
name: "CaptioningConfig".to_string(),
fields: vec![
FieldDef {
name: "llm".to_string(),
ty: TypeRef::Named("LlmConfig".to_string()),
..Default::default()
},
FieldDef {
name: "alt_text".to_string(),
ty: TypeRef::Named("CaptionAltTextMode".to_string()),
default: Some("/* serde(default) */".to_string()),
typed_default: Some(crate::core::ir::DefaultValue::EnumVariant("Preserve".to_string())),
..Default::default()
},
],
..Default::default()
};
let stub = gen_type_init_stub(
&typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&OptionsFieldBridges::default(),
);
assert!(stub.contains("llm: LlmConfig"), "{stub}");
assert!(!stub.contains("llm: LlmConfig ="), "{stub}");
assert!(stub.contains("alt_text: CaptionAltTextMode = ..."), "{stub}");
assert!(!stub.contains("alt_text: CaptionAltTextMode | None"), "{stub}");
}
#[test]
fn type_init_stub_preserves_middle_bare_serde_default_as_required() {
let typ = TypeDef {
name: "CaptioningConfig".to_string(),
fields: vec![
FieldDef {
name: "llm".to_string(),
ty: TypeRef::Named("LlmConfig".to_string()),
..Default::default()
},
FieldDef {
name: "alt_text".to_string(),
ty: TypeRef::Named("CaptionAltTextMode".to_string()),
default: Some("/* serde(default) */".to_string()),
typed_default: Some(crate::core::ir::DefaultValue::EnumVariant("Preserve".to_string())),
..Default::default()
},
FieldDef {
name: "provider".to_string(),
ty: TypeRef::Named("CaptionProvider".to_string()),
..Default::default()
},
],
..Default::default()
};
let stub = gen_type_init_stub(
&typ,
&ApiSurface::default(),
&ResolvedCrateConfig::default(),
&OptionsFieldBridges::default(),
);
assert_eq!(
stub,
" def __init__(\n self,\n llm: LlmConfig,\n alt_text: CaptionAltTextMode,\n provider: CaptionProvider,\n ) -> None: ..."
);
}
}