use std::collections::{HashMap, HashSet};
use crate::apidoc::ApidocDict;
use crate::apidoc_patches::ApidocPatchSet;
use crate::ast::{AssertKind, BlockItem, Expr, ExprKind};
use crate::c_fn_decl::CFnDeclDict;
use crate::fields_dict::FieldsDict;
use crate::inline_fn::InlineFnDict;
use crate::intern::{InternedStr, StringInterner};
use crate::macro_def::{MacroDef, MacroKind, MacroTable};
use crate::parser::{
parse_expression_from_tokens_ref_with_stats,
parse_expression_from_tokens_ref_with_generic_params,
parse_statement_from_tokens_ref_with_stats,
parse_statement_from_tokens_ref_with_generic_params,
parse_block_items_from_tokens_ref_with_stats,
parse_block_items_from_tokens_ref_with_generic_params,
ParseStats,
};
use crate::rust_decl::RustDeclDict;
use crate::semantic::SemanticAnalyzer;
use crate::preprocessor::Preprocessor;
use crate::source::FileRegistry;
use crate::token::{Token, TokenKind};
use crate::type_env::{TypeConstraint, TypeEnv};
use crate::type_repr::TypeRepr;
#[derive(Debug, Clone, Copy)]
pub struct NoExpandSymbols {
pub assert: InternedStr,
pub assert_: InternedStr,
}
impl NoExpandSymbols {
pub fn new(interner: &mut StringInterner) -> Self {
Self {
assert: interner.intern("assert"),
assert_: interner.intern("assert_"),
}
}
pub fn iter(&self) -> impl Iterator<Item = InternedStr> {
[self.assert, self.assert_].into_iter()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExplicitExpandSymbols {
pub sv_any: InternedStr,
pub sv_flags: InternedStr,
pub cv_flags: InternedStr,
pub hek_flags: InternedStr,
pub expect: InternedStr,
pub likely: InternedStr,
pub unlikely: InternedStr,
pub cbool: InternedStr,
pub assert_underscore_: InternedStr,
pub str_with_len: InternedStr,
pub int2ptr: InternedStr,
pub assert_not_rok: InternedStr,
pub assert_not_glob: InternedStr,
pub mutable_ptr: InternedStr,
}
impl ExplicitExpandSymbols {
pub fn new(interner: &mut StringInterner) -> Self {
Self {
sv_any: interner.intern("SvANY"),
sv_flags: interner.intern("SvFLAGS"),
cv_flags: interner.intern("CvFLAGS"),
hek_flags: interner.intern("HEK_FLAGS"),
expect: interner.intern("EXPECT"),
likely: interner.intern("LIKELY"),
unlikely: interner.intern("UNLIKELY"),
cbool: interner.intern("cBOOL"),
assert_underscore_: interner.intern("__ASSERT_"),
str_with_len: interner.intern("STR_WITH_LEN"),
int2ptr: interner.intern("INT2PTR"),
assert_not_rok: interner.intern("assert_not_ROK"),
assert_not_glob: interner.intern("assert_not_glob"),
mutable_ptr: interner.intern("MUTABLE_PTR"),
}
}
pub fn iter(&self) -> impl Iterator<Item = InternedStr> {
[
self.sv_any,
self.sv_flags,
self.cv_flags,
self.hek_flags,
self.expect,
self.likely,
self.unlikely,
self.cbool,
self.assert_underscore_,
self.str_with_len,
self.int2ptr,
self.assert_not_rok,
self.assert_not_glob,
self.mutable_ptr,
].into_iter()
}
}
#[derive(Debug, Clone)]
pub enum ParseResult {
Expression(Box<Expr>),
Statement(Vec<BlockItem>),
Unparseable(Option<String>),
}
#[derive(Debug, Clone)]
pub struct MacroParam {
pub name: InternedStr,
pub expr: Expr,
}
impl MacroParam {
pub fn new(name: InternedStr, loc: crate::source::SourceLocation) -> Self {
Self {
name,
expr: Expr::new(ExprKind::Ident(name), loc),
}
}
pub fn expr_id(&self) -> crate::ast::ExprId {
self.expr.id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InferStatus {
Pending,
TypeComplete,
TypeIncomplete,
TypeUnknown,
}
impl Default for InferStatus {
fn default() -> Self {
Self::Pending
}
}
fn collect_literal_string_params(entry: &crate::apidoc::ApidocEntry, info: &mut MacroInferInfo) {
use crate::apidoc::ApidocEntry;
for (i, arg) in entry.args.iter().enumerate() {
if ApidocEntry::is_literal_string_keyword(&arg.ty) {
info.literal_string_params.insert(i);
}
}
}
fn collect_generic_params(entry: &crate::apidoc::ApidocEntry, info: &mut MacroInferInfo) {
use crate::apidoc::ApidocEntry;
const PARAM_NAMES: [char; 7] = ['T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
let mut param_idx = 0;
for (i, arg) in entry.args.iter().enumerate() {
if ApidocEntry::is_type_param_keyword(&arg.ty) {
if param_idx < PARAM_NAMES.len() {
let name = PARAM_NAMES[param_idx].to_string();
info.generic_type_params.insert(i as i32, name);
param_idx += 1;
}
}
}
if entry.returns_type_param() {
let name = if let Some(first_name) = info.generic_type_params.get(&0) {
first_name.clone()
} else if param_idx < PARAM_NAMES.len() {
PARAM_NAMES[param_idx].to_string()
} else {
"T".to_string()
};
info.generic_type_params.insert(-1, name); }
}
#[derive(Debug, Clone)]
pub struct MacroInferInfo {
pub name: InternedStr,
pub is_target: bool,
pub has_body: bool,
pub is_function: bool,
pub uses: HashSet<InternedStr>,
pub used_by: HashSet<InternedStr>,
pub is_thx_dependent: bool,
pub has_token_pasting: bool,
pub params: Vec<MacroParam>,
pub parse_result: ParseResult,
pub type_env: TypeEnv,
pub args_infer_status: InferStatus,
pub return_infer_status: InferStatus,
pub generic_type_params: HashMap<i32, String>,
pub literal_string_params: HashSet<usize>,
pub function_call_count: usize,
pub deref_count: usize,
pub called_functions: HashSet<InternedStr>,
pub calls_unavailable: bool,
pub apidoc_suppressed: bool,
pub resolved_param_types: Vec<String>,
pub resolved_return_type: Option<String>,
pub const_pointer_positions: HashSet<usize>,
pub is_bool_return: bool,
}
impl MacroInferInfo {
pub fn new(name: InternedStr) -> Self {
Self {
name,
is_target: false,
has_body: false,
is_function: false,
uses: HashSet::new(),
used_by: HashSet::new(),
is_thx_dependent: false,
has_token_pasting: false,
params: Vec::new(),
parse_result: ParseResult::Unparseable(None),
type_env: TypeEnv::new(),
args_infer_status: InferStatus::Pending,
return_infer_status: InferStatus::Pending,
generic_type_params: HashMap::new(),
literal_string_params: HashSet::new(),
function_call_count: 0,
deref_count: 0,
called_functions: HashSet::new(),
calls_unavailable: false,
apidoc_suppressed: false,
resolved_param_types: Vec::new(),
resolved_return_type: None,
const_pointer_positions: HashSet::new(),
is_bool_return: false,
}
}
pub fn has_unsafe_ops(&self) -> bool {
self.function_call_count > 0 || self.deref_count > 0
}
pub fn is_unavailable_for_codegen(&self) -> bool {
self.calls_unavailable || self.apidoc_suppressed
}
pub fn find_param_expr_id(&self, name: InternedStr) -> Option<crate::ast::ExprId> {
self.params.iter()
.find(|p| p.name == name)
.map(|p| p.expr_id())
}
pub fn is_fully_confirmed(&self) -> bool {
self.args_infer_status == InferStatus::TypeComplete
&& self.return_infer_status == InferStatus::TypeComplete
}
pub fn add_use(&mut self, used_macro: InternedStr) {
self.uses.insert(used_macro);
}
pub fn add_used_by(&mut self, user_macro: InternedStr) {
self.used_by.insert(user_macro);
}
pub fn is_expression(&self) -> bool {
matches!(self.parse_result, ParseResult::Expression(_))
}
pub fn is_statement(&self) -> bool {
matches!(self.parse_result, ParseResult::Statement(_))
}
pub fn is_parseable(&self) -> bool {
!matches!(self.parse_result, ParseResult::Unparseable(_))
}
pub fn get_return_type(&self) -> Option<&crate::type_repr::TypeRepr> {
let mut best: Option<(&crate::type_repr::TypeRepr, u8)> = None;
for c in &self.type_env.return_constraints {
let tier = c.ty.confidence_tier();
if best.is_none() || tier < best.unwrap().1 {
best = Some((&c.ty, tier));
}
}
if let ParseResult::Expression(ref expr) = self.parse_result {
if let Some(constraints) = self.type_env.get_expr_constraints(expr.id) {
for c in constraints {
let tier = c.ty.confidence_tier();
if best.is_none() || tier < best.unwrap().1 {
best = Some((&c.ty, tier));
}
}
}
}
best.map(|(ty, _)| ty)
}
}
pub struct MacroInferContext {
pub macros: HashMap<InternedStr, MacroInferInfo>,
pub confirmed: HashSet<InternedStr>,
pub unconfirmed: HashSet<InternedStr>,
pub unknown: HashSet<InternedStr>,
pub debug_macros: HashSet<String>,
pub macro_param_types: HashMap<String, Vec<(String, String)>>,
}
impl MacroInferContext {
pub fn new() -> Self {
Self {
macros: HashMap::new(),
confirmed: HashSet::new(),
unconfirmed: HashSet::new(),
unknown: HashSet::new(),
debug_macros: HashSet::new(),
macro_param_types: HashMap::new(),
}
}
pub fn set_debug_macros(&mut self, macros: impl IntoIterator<Item = String>) {
self.debug_macros = macros.into_iter().collect();
}
pub fn is_debug_target(&self, name: &str) -> bool {
self.debug_macros.contains(name)
}
pub fn register(&mut self, info: MacroInferInfo) {
let name = info.name;
self.macros.insert(name, info);
}
pub fn get(&self, name: InternedStr) -> Option<&MacroInferInfo> {
self.macros.get(&name)
}
pub fn get_mut(&mut self, name: InternedStr) -> Option<&mut MacroInferInfo> {
self.macros.get_mut(&name)
}
pub fn apply_apidoc_suppressions(
&mut self,
patches: &ApidocPatchSet,
interner: &StringInterner,
) -> usize {
let mut count = 0usize;
for name_str in patches.skip_codegen.keys() {
if let Some(interned) = interner.lookup(name_str) {
if let Some(info) = self.macros.get_mut(&interned) {
info.apidoc_suppressed = true;
count += 1;
}
}
}
count
}
pub fn build_use_relations(&mut self) {
let use_pairs: Vec<(InternedStr, InternedStr)> = self
.macros
.iter()
.flat_map(|(user, info)| {
info.uses
.iter()
.map(move |used| (*user, *used))
})
.collect();
for (user, used) in use_pairs {
if let Some(used_info) = self.macros.get_mut(&used) {
used_info.add_used_by(user);
}
}
}
pub fn classify_initial(&mut self) {
for (name, info) in &self.macros {
if info.is_fully_confirmed() {
self.confirmed.insert(*name);
} else if info.args_infer_status == InferStatus::TypeUnknown
|| info.return_infer_status == InferStatus::TypeUnknown
{
self.unknown.insert(*name);
} else {
self.unconfirmed.insert(*name);
}
}
}
pub fn get_inference_candidates(&self) -> Vec<InternedStr> {
let mut candidates: Vec<_> = self
.unconfirmed
.iter()
.filter(|name| {
if let Some(info) = self.macros.get(name) {
info.uses.iter().all(|used| {
self.confirmed.contains(used) || !self.macros.contains_key(used)
})
} else {
false
}
})
.copied()
.collect();
candidates.sort_by_key(|name| {
self.macros
.get(name)
.map(|info| info.uses.len())
.unwrap_or(0)
});
candidates
}
pub fn mark_confirmed(&mut self, name: InternedStr) {
self.unconfirmed.remove(&name);
self.confirmed.insert(name);
if let Some(info) = self.macros.get_mut(&name) {
info.args_infer_status = InferStatus::TypeComplete;
info.return_infer_status = InferStatus::TypeComplete;
}
}
pub fn cache_param_types(&mut self, name: InternedStr, interner: &StringInterner) {
let mut temp_cache = HashMap::new();
self.cache_param_types_to(name, interner, &mut temp_cache);
self.macro_param_types.extend(temp_cache);
}
pub fn cache_param_types_to(
&self,
name: InternedStr,
interner: &StringInterner,
cache: &mut HashMap<String, Vec<(String, String)>>,
) {
let info = match self.macros.get(&name) {
Some(info) => info,
None => return,
};
let macro_name = interner.get(name).to_string();
let mut param_types = Vec::new();
for param in &info.params {
let param_name = interner.get(param.name).to_string();
let type_str = if let Some(expr_ids) = info.type_env.param_to_exprs.get(¶m.name) {
let mut found_type = None;
for expr_id in expr_ids {
if let Some(constraints) = info.type_env.expr_constraints.get(expr_id) {
for c in constraints {
if !c.ty.is_void() {
found_type = Some(c.ty.to_rust_string(interner));
break;
}
}
}
if found_type.is_some() {
break;
}
}
found_type
} else {
let expr_id = param.expr_id();
info.type_env.expr_constraints.get(&expr_id)
.and_then(|constraints| constraints.first())
.map(|c| c.ty.to_rust_string(interner))
};
if let Some(ty) = type_str {
param_types.push((param_name, ty));
}
}
if !param_types.is_empty() {
cache.insert(macro_name, param_types);
}
}
pub fn get_macro_param_types(&self) -> &HashMap<String, Vec<(String, String)>> {
&self.macro_param_types
}
pub fn mark_args_unknown(&mut self, name: InternedStr) {
if let Some(info) = self.macros.get_mut(&name) {
info.args_infer_status = InferStatus::TypeUnknown;
}
}
pub fn mark_return_unknown(&mut self, name: InternedStr) {
if let Some(info) = self.macros.get_mut(&name) {
info.return_infer_status = InferStatus::TypeUnknown;
}
}
pub fn move_to_unknown(&mut self, name: InternedStr) {
self.unconfirmed.remove(&name);
self.unknown.insert(name);
}
pub fn stats(&self) -> MacroInferStats {
let mut args_unknown = 0;
let mut return_unknown = 0;
for info in self.macros.values() {
if info.args_infer_status == InferStatus::TypeUnknown {
args_unknown += 1;
}
if info.return_infer_status == InferStatus::TypeUnknown {
return_unknown += 1;
}
}
MacroInferStats {
total: self.macros.len(),
confirmed: self.confirmed.len(),
unconfirmed: self.unconfirmed.len(),
args_unknown,
return_unknown,
}
}
pub fn build_macro_info(
&self,
def: &MacroDef,
pp: &mut Preprocessor,
typedefs: &HashSet<InternedStr>,
thx_symbols: (InternedStr, InternedStr, InternedStr),
no_expand: NoExpandSymbols,
perl_build_mode: crate::perl_config::PerlBuildMode,
) -> (MacroInferInfo, bool, bool) {
let mut info = MacroInferInfo::new(def.name);
info.is_target = def.is_target;
info.has_body = !def.body.is_empty();
info.is_function = matches!(def.kind, MacroKind::Function { .. });
let params: Vec<InternedStr> = if let MacroKind::Function { params, .. } = &def.kind {
for ¶m_name in params {
info.params.push(MacroParam::new(param_name, crate::source::SourceLocation::default()));
}
params.clone()
} else {
Vec::new()
};
let has_pasting_direct = def.body.iter().any(|t| matches!(t.kind, TokenKind::HashHash));
for sym in no_expand.iter() {
pp.add_skip_expand_macro(sym);
}
let mut in_progress = HashSet::new();
in_progress.insert(def.name);
let (expanded_tokens, called_macros) = match pp.expand_macro_body_for_inference(
&def.body,
¶ms,
&[], &mut in_progress,
) {
Ok(result) => result,
Err(_) => {
(def.body.clone(), HashSet::new())
}
};
let has_cannot = expanded_tokens.iter().any(|t| {
matches!(&t.kind, TokenKind::StringLit(s) if s == b"CANNOT")
});
if has_cannot {
info.calls_unavailable = true;
return (info, has_pasting_direct, false);
}
let expanded_tokens = inject_comma_after_assert_underscore(
&expanded_tokens,
&no_expand,
);
self.collect_uses_from_called(&called_macros, &mut info);
let (sym_athx, sym_tthx, sym_my_perl) = thx_symbols;
let has_thx = if perl_build_mode.is_threaded() {
let has_thx_from_uses = info.uses.contains(&sym_athx) || info.uses.contains(&sym_tthx);
let has_my_perl = expanded_tokens.iter().any(|t| {
matches!(t.kind, TokenKind::Ident(id) if id == sym_my_perl)
});
has_thx_from_uses || has_my_perl
} else {
false
};
info.has_token_pasting = has_pasting_direct;
info.is_thx_dependent = has_thx;
let interner = pp.interner();
let files = pp.files();
let generic_params: HashMap<InternedStr, usize> = params.iter()
.enumerate()
.map(|(i, &name)| (name, i))
.collect();
let (parse_result, stats, detected_type_params) = self.try_parse_tokens(
&expanded_tokens, interner, files, typedefs, generic_params,
);
info.parse_result = parse_result;
info.function_call_count = stats.function_call_count;
info.deref_count = stats.deref_count;
if !detected_type_params.is_empty() {
let param_names = ['T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
let mut idx = 0;
for (i, param) in params.iter().enumerate() {
if detected_type_params.contains(param) && idx < param_names.len() {
info.generic_type_params.insert(i as i32, param_names[idx].to_string());
idx += 1;
}
}
}
match &mut info.parse_result {
ParseResult::Expression(expr) => {
convert_assert_calls(expr, interner);
}
ParseResult::Statement(items) => {
for item in items {
if let BlockItem::Stmt(stmt) = item {
convert_assert_calls_in_stmt(stmt, interner);
}
}
}
ParseResult::Unparseable(_) => {}
}
match &info.parse_result {
ParseResult::Expression(expr) => {
Self::collect_function_calls_from_expr(expr, &mut info.called_functions);
}
ParseResult::Statement(block_items) => {
Self::collect_function_calls_from_block_items(block_items, &mut info.called_functions);
}
ParseResult::Unparseable(_) => {}
}
(info, has_pasting_direct, has_thx)
}
pub fn infer_macro_types<'a>(
&mut self,
name: InternedStr,
params: &[InternedStr],
interner: &'a StringInterner,
files: &'a FileRegistry,
apidoc: Option<&'a ApidocDict>,
fields_dict: Option<&'a FieldsDict>,
rust_decl_dict: Option<&'a RustDeclDict>,
inline_fn_dict: Option<&'a InlineFnDict>,
typedefs: &'a HashSet<InternedStr>,
return_types_cache: &HashMap<String, String>,
param_types_cache: &HashMap<String, Vec<(String, String)>>,
) {
let macro_name_str = interner.get(name);
let is_debug = self.is_debug_target(macro_name_str);
if is_debug {
eprintln!("\n[DEBUG infer_macro_types] macro={}", macro_name_str);
eprintln!(" params: {:?}", params.iter().map(|p| interner.get(*p)).collect::<Vec<_>>());
}
let info = match self.macros.get_mut(&name) {
Some(info) => info,
None => return,
};
if let ParseResult::Expression(ref expr) = info.parse_result {
let mut analyzer = SemanticAnalyzer::with_rust_decl_dict(
interner,
apidoc,
fields_dict,
rust_decl_dict,
inline_fn_dict,
);
analyzer.set_macro_return_types(return_types_cache);
analyzer.set_macro_param_types(param_types_cache);
analyzer.register_macro_params_from_apidoc(name, params, files, typedefs);
analyzer.collect_expr_constraints(expr, &mut info.type_env);
if is_debug {
eprintln!(" [type_env after collect_expr_constraints]");
for (expr_id, constraints) in &info.type_env.expr_constraints {
for c in constraints {
eprintln!(" expr_id={:?}: {} ({})", expr_id, c.ty.to_display_string(interner), c.context);
}
}
eprintln!(" [param_constraints]");
for (param_id, constraints) in &info.type_env.param_constraints {
for c in constraints {
eprintln!(" param={}: {} ({})", interner.get(*param_id), c.ty.to_display_string(interner), c.context);
}
}
eprintln!(" [param_to_exprs]");
for (param, expr_ids) in &info.type_env.param_to_exprs {
eprintln!(" param={}: {:?}", interner.get(*param), expr_ids);
}
}
if let Some(apidoc_dict) = apidoc {
let macro_name_str = interner.get(name);
if let Some(entry) = apidoc_dict.get(macro_name_str) {
if let Some(ref return_type) = entry.return_type {
let type_repr = TypeRepr::from_c_type_string(return_type, interner, files, typedefs);
info.type_env.add_return_constraint(TypeConstraint::new(
expr.id,
type_repr,
format!("return type of macro {}", macro_name_str),
));
}
collect_generic_params(entry, info);
collect_literal_string_params(entry, info);
}
}
}
if let ParseResult::Statement(ref block_items) = info.parse_result {
let mut analyzer = SemanticAnalyzer::with_rust_decl_dict(
interner,
apidoc,
fields_dict,
rust_decl_dict,
inline_fn_dict,
);
analyzer.set_macro_return_types(return_types_cache);
analyzer.set_macro_param_types(param_types_cache);
analyzer.register_macro_params_from_apidoc(name, params, files, typedefs);
for item in block_items {
if let BlockItem::Stmt(stmt) = item {
analyzer.collect_stmt_constraints(stmt, &mut info.type_env);
}
}
}
}
pub fn get_macro_return_type(&self, name: InternedStr, interner: &StringInterner) -> Option<(String, String)> {
self.macros.get(&name).and_then(|info| {
info.get_return_type().map(|ty| {
(interner.get(name).to_string(), ty.to_rust_string(interner))
})
})
}
fn collect_uses_from_called(
&self,
called_macros: &HashSet<InternedStr>,
info: &mut MacroInferInfo,
) {
for &id in called_macros {
if id != info.name {
info.add_use(id);
}
}
}
fn has_toplevel_semicolon(tokens: &[Token]) -> bool {
let mut depth = 0;
for t in tokens {
match t.kind {
TokenKind::LParen | TokenKind::LBrace | TokenKind::LBracket => depth += 1,
TokenKind::RParen | TokenKind::RBrace | TokenKind::RBracket => {
if depth > 0 { depth -= 1; }
}
TokenKind::Semi if depth == 0 => return true,
_ => {}
}
}
false
}
fn try_parse_tokens(
&self,
tokens: &[crate::token::Token],
interner: &StringInterner,
files: &FileRegistry,
typedefs: &HashSet<InternedStr>,
generic_params: HashMap<InternedStr, usize>,
) -> (ParseResult, ParseStats, HashSet<InternedStr>) {
if tokens.is_empty() {
return (ParseResult::Unparseable(Some("empty token sequence".to_string())), ParseStats::default(), HashSet::new());
}
let first_significant = tokens.iter().find(|t| {
!matches!(t.kind, TokenKind::Space | TokenKind::Newline)
});
let is_statement_start = first_significant
.is_some_and(|t| matches!(t.kind, TokenKind::KwDo | TokenKind::KwIf));
if is_statement_start {
if generic_params.is_empty() {
match parse_statement_from_tokens_ref_with_stats(tokens.to_vec(), interner, files, typedefs) {
Ok((stmt, stats)) => {
return (
ParseResult::Statement(vec![BlockItem::Stmt(stmt)]),
stats,
HashSet::new(),
);
}
Err(_) => {} }
} else {
match parse_statement_from_tokens_ref_with_generic_params(tokens.to_vec(), interner, files, typedefs, generic_params.clone()) {
Ok((stmt, stats, detected)) => {
return (
ParseResult::Statement(vec![BlockItem::Stmt(stmt)]),
stats,
detected,
);
}
Err(_) => {} }
}
}
if Self::has_toplevel_semicolon(tokens) {
if generic_params.is_empty() {
match parse_block_items_from_tokens_ref_with_stats(tokens.to_vec(), interner, files, typedefs) {
Ok((items, stats)) => {
return (
ParseResult::Statement(items),
stats,
HashSet::new(),
);
}
Err(_) => {} }
} else {
match parse_block_items_from_tokens_ref_with_generic_params(tokens.to_vec(), interner, files, typedefs, generic_params.clone()) {
Ok((items, stats, detected)) => {
return (
ParseResult::Statement(items),
stats,
detected,
);
}
Err(_) => {} }
}
}
if generic_params.is_empty() {
match parse_expression_from_tokens_ref_with_stats(tokens.to_vec(), interner, files, typedefs) {
Ok((expr, stats)) => (
ParseResult::Expression(Box::new(expr)),
stats,
HashSet::new(),
),
Err(err) => (ParseResult::Unparseable(Some(err.format_with_files(files))), ParseStats::default(), HashSet::new()),
}
} else {
match parse_expression_from_tokens_ref_with_generic_params(tokens.to_vec(), interner, files, typedefs, generic_params) {
Ok((expr, stats, detected)) => (
ParseResult::Expression(Box::new(expr)),
stats,
detected,
),
Err(err) => (ParseResult::Unparseable(Some(err.format_with_files(files))), ParseStats::default(), HashSet::new()),
}
}
}
pub fn analyze_all_macros<'a>(
&mut self,
pp: &mut Preprocessor,
apidoc: Option<&'a ApidocDict>,
apidoc_patches: Option<&'a ApidocPatchSet>,
fields_dict: Option<&'a FieldsDict>,
rust_decl_dict: Option<&'a RustDeclDict>,
mut inline_fn_dict: Option<&'a mut InlineFnDict>,
c_fn_decl_dict: Option<&'a CFnDeclDict>,
typedefs: &HashSet<InternedStr>,
thx_symbols: (InternedStr, InternedStr, InternedStr),
no_expand: NoExpandSymbols,
perl_build_mode: crate::perl_config::PerlBuildMode,
) {
let mut thx_initial = HashSet::new();
let mut pasting_initial = HashSet::new();
let target_macros: Vec<MacroDef> = pp.macros().iter_target_macros().cloned().collect();
for def in &target_macros {
let (info, has_pasting, has_thx) = self.build_macro_info(
def, pp, typedefs, thx_symbols, no_expand, perl_build_mode
);
if has_pasting {
pasting_initial.insert(def.name);
}
if has_thx {
thx_initial.insert(def.name);
}
self.register(info);
}
if let Some(c_fn_dict) = c_fn_decl_dict {
for (name, info) in &self.macros {
let has_thx_from_fn_calls = info.called_functions.iter().any(|fn_name| {
c_fn_dict.is_thx_dependent(*fn_name)
});
if has_thx_from_fn_calls && !thx_initial.contains(name) {
thx_initial.insert(*name);
}
}
}
self.build_use_relations();
self.propagate_flag_via_used_by(&thx_initial, true);
self.propagate_flag_via_used_by(&pasting_initial, false);
if let Some(patches) = apidoc_patches {
let interner = pp.interner();
let macro_hits = self.apply_apidoc_suppressions(patches, interner);
let inline_hits = inline_fn_dict
.as_mut()
.map(|ifd| ifd.apply_apidoc_suppressions(patches, interner))
.unwrap_or(0);
let total = patches.skip_codegen.len();
let unmatched = total.saturating_sub(macro_hits + inline_hits);
eprintln!(
"[apidoc-suppress] skip_codegen reflected: {} macro(s) + {} inline fn(s); \
{} of {} entries unmatched (no such macro/inline; possibly stale skip-list)",
macro_hits, inline_hits, unmatched, total,
);
}
{
let interner = pp.interner();
self.check_function_availability(
rust_decl_dict,
inline_fn_dict.as_deref(),
interner,
);
}
if let Some(ref mut ifd) = inline_fn_dict {
let interner = pp.interner();
self.check_inline_fn_availability(ifd, rust_decl_dict, interner);
}
if let Some(ref mut ifd) = inline_fn_dict {
self.propagate_unavailable_cross_domain(ifd);
} else {
self.propagate_unavailable_via_used_by();
}
for name in self.macros.keys().copied().collect::<Vec<_>>() {
self.unconfirmed.insert(name);
}
{
let macro_table = pp.macros();
let interner = pp.interner();
let files = pp.files();
self.infer_types_in_dependency_order(
macro_table, interner, files, apidoc, fields_dict, rust_decl_dict,
inline_fn_dict.as_deref(), typedefs
);
}
}
fn propagate_flag_via_used_by(&mut self, initial_set: &HashSet<InternedStr>, is_thx: bool) {
for name in initial_set {
if let Some(info) = self.macros.get_mut(name) {
if is_thx {
info.is_thx_dependent = true;
} else {
info.has_token_pasting = true;
}
}
}
let mut to_propagate: Vec<InternedStr> = initial_set.iter().copied().collect();
while let Some(name) = to_propagate.pop() {
let used_by_list: Vec<InternedStr> = self.macros
.get(&name)
.map(|info| info.used_by.iter().copied().collect())
.unwrap_or_default();
for user in used_by_list {
if let Some(user_info) = self.macros.get_mut(&user) {
let flag = if is_thx {
&mut user_info.is_thx_dependent
} else {
&mut user_info.has_token_pasting
};
if !*flag {
*flag = true;
to_propagate.push(user);
}
}
}
}
}
fn check_function_availability(
&mut self,
rust_decl_dict: Option<&RustDeclDict>,
inline_fn_dict: Option<&InlineFnDict>,
interner: &StringInterner,
) {
let bindings_fns: std::collections::HashSet<&str> = rust_decl_dict
.map(|d| d.fns.keys().map(|s| s.as_str()).collect())
.unwrap_or_default();
let builtin_fns: std::collections::HashSet<&str> = [
"__builtin_expect",
"__builtin_offsetof",
"offsetof",
"__builtin_types_compatible_p",
"__builtin_constant_p",
"__builtin_choose_expr",
"__builtin_unreachable",
"__builtin_trap",
"__builtin_assume",
"__builtin_bswap16",
"__builtin_bswap32",
"__builtin_bswap64",
"__builtin_popcount",
"__builtin_clz",
"__builtin_ctz",
"pthread_mutex_lock",
"pthread_mutex_unlock",
"pthread_rwlock_rdlock",
"pthread_rwlock_wrlock",
"pthread_rwlock_unlock",
"memchr",
"memcpy",
"memmove",
"memset",
"strlen",
"strcmp",
"strncmp",
"strcpy",
"strncpy",
"ASSERT_IS_LITERAL",
"ASSERT_IS_PTR",
"ASSERT_NOT_PTR",
].into_iter().collect();
let macro_names: HashSet<InternedStr> = self.macros.keys().copied().collect();
let macro_names_list: Vec<InternedStr> = self.macros.keys().copied().collect();
for name in macro_names_list {
let called_functions: Vec<InternedStr> = self.macros
.get(&name)
.map(|info| info.called_functions.iter().copied().collect())
.unwrap_or_default();
let mut has_unavailable = false;
for called_fn in called_functions {
let fn_name = interner.get(called_fn);
if macro_names.contains(&called_fn) {
continue;
}
if bindings_fns.contains(fn_name) {
continue;
}
if let Some(inline_fns) = inline_fn_dict {
if inline_fns.get(called_fn).is_some() {
continue;
}
}
if builtin_fns.contains(fn_name) {
continue;
}
has_unavailable = true;
break;
}
if has_unavailable {
if let Some(info) = self.macros.get_mut(&name) {
info.calls_unavailable = true;
}
}
}
}
fn propagate_unavailable_via_used_by(&mut self) {
let initial_set: HashSet<InternedStr> = self.macros
.iter()
.filter(|(_, info)| info.is_unavailable_for_codegen())
.map(|(name, _)| *name)
.collect();
let mut to_propagate: Vec<InternedStr> = initial_set.into_iter().collect();
while let Some(name) = to_propagate.pop() {
let used_by_list: Vec<InternedStr> = self.macros
.get(&name)
.map(|info| info.used_by.iter().copied().collect())
.unwrap_or_default();
for user in used_by_list {
if let Some(user_info) = self.macros.get_mut(&user) {
if !user_info.calls_unavailable {
user_info.calls_unavailable = true;
to_propagate.push(user);
}
}
}
}
}
fn check_inline_fn_availability(
&self,
inline_fn_dict: &mut InlineFnDict,
rust_decl_dict: Option<&RustDeclDict>,
interner: &StringInterner,
) {
let bindings_fns: HashSet<&str> = rust_decl_dict
.map(|d| d.fns.keys().map(|s| s.as_str()).collect())
.unwrap_or_default();
let builtin_fns: HashSet<&str> = [
"__builtin_expect",
"__builtin_offsetof",
"offsetof",
"__builtin_types_compatible_p",
"__builtin_constant_p",
"__builtin_choose_expr",
"__builtin_unreachable",
"__builtin_trap",
"__builtin_assume",
"__builtin_bswap16",
"__builtin_bswap32",
"__builtin_bswap64",
"__builtin_popcount",
"__builtin_clz",
"__builtin_ctz",
"pthread_mutex_lock",
"pthread_mutex_unlock",
"pthread_rwlock_rdlock",
"pthread_rwlock_wrlock",
"pthread_rwlock_unlock",
"memchr",
"memcpy",
"memmove",
"memset",
"strlen",
"strcmp",
"strncmp",
"strcpy",
"strncpy",
"ASSERT_IS_LITERAL",
"ASSERT_IS_PTR",
"ASSERT_NOT_PTR",
].into_iter().collect();
let macro_names: HashSet<InternedStr> = self.macros.keys().copied().collect();
let entries: Vec<(InternedStr, Vec<InternedStr>)> = inline_fn_dict
.called_functions_iter()
.map(|(name, calls)| (*name, calls.iter().copied().collect()))
.collect();
for (name, called_fns) in entries {
let mut has_unavailable = false;
for called_fn in called_fns {
let fn_name = interner.get(called_fn);
if macro_names.contains(&called_fn) { continue; }
if bindings_fns.contains(fn_name) { continue; }
if inline_fn_dict.get(called_fn).is_some() { continue; }
if builtin_fns.contains(fn_name) { continue; }
has_unavailable = true;
break;
}
if has_unavailable {
inline_fn_dict.set_calls_unavailable(name);
}
}
}
fn propagate_unavailable_cross_domain(
&mut self,
inline_fn_dict: &mut InlineFnDict,
) {
loop {
let mut changed = false;
let macro_names: Vec<InternedStr> = self.macros.keys().copied().collect();
for name in ¯o_names {
if !self.macros.get(name)
.map(|i| i.is_unavailable_for_codegen())
.unwrap_or(false)
{
continue;
}
let used_by_list: Vec<InternedStr> = self.macros
.get(name)
.map(|info| info.used_by.iter().copied().collect())
.unwrap_or_default();
for user in used_by_list {
if let Some(user_info) = self.macros.get_mut(&user) {
if !user_info.calls_unavailable {
user_info.calls_unavailable = true;
changed = true;
}
}
}
}
let inline_entries: Vec<(InternedStr, Vec<InternedStr>)> = inline_fn_dict
.called_functions_iter()
.map(|(name, calls)| (*name, calls.iter().copied().collect()))
.collect();
for (name, calls) in &inline_entries {
if inline_fn_dict.is_calls_unavailable(*name) {
continue;
}
let has_unavailable_inline = calls.iter().any(|called| {
inline_fn_dict.get(*called).is_some()
&& inline_fn_dict.is_unavailable_for_codegen(*called)
});
if has_unavailable_inline {
inline_fn_dict.set_calls_unavailable(*name);
changed = true;
}
}
for name in ¯o_names {
if self.macros.get(name)
.map(|i| i.calls_unavailable)
.unwrap_or(false)
{
continue;
}
let called_fns: Vec<InternedStr> = self.macros
.get(name)
.map(|info| info.called_functions.iter().copied().collect())
.unwrap_or_default();
let has_unavailable_inline = called_fns.iter().any(|called| {
inline_fn_dict.get(*called).is_some()
&& inline_fn_dict.is_unavailable_for_codegen(*called)
});
if has_unavailable_inline {
if let Some(info) = self.macros.get_mut(name) {
info.calls_unavailable = true;
changed = true;
}
}
}
for (name, calls) in &inline_entries {
if inline_fn_dict.is_calls_unavailable(*name) {
continue;
}
let has_unavailable_macro = calls.iter().any(|called| {
self.macros.get(called)
.map(|info| info.is_unavailable_for_codegen())
.unwrap_or(false)
});
if has_unavailable_macro {
inline_fn_dict.set_calls_unavailable(*name);
changed = true;
}
}
if !changed {
break;
}
}
}
fn infer_types_in_dependency_order<'a>(
&mut self,
macro_table: &MacroTable,
interner: &'a StringInterner,
files: &FileRegistry,
apidoc: Option<&'a ApidocDict>,
fields_dict: Option<&'a FieldsDict>,
rust_decl_dict: Option<&'a RustDeclDict>,
inline_fn_dict: Option<&'a InlineFnDict>,
typedefs: &HashSet<InternedStr>,
) {
let mut return_types_cache: HashMap<String, String> = HashMap::new();
let mut param_types_cache: HashMap<String, Vec<(String, String)>> = HashMap::new();
loop {
let candidates = self.get_inference_candidates();
if candidates.is_empty() {
let remaining: Vec<_> = self.unconfirmed.iter().copied().collect();
for name in remaining {
let params: Vec<InternedStr> = macro_table
.get(name)
.map(|def| match &def.kind {
MacroKind::Function { params, .. } => params.clone(),
MacroKind::Object => vec![],
})
.unwrap_or_default();
self.infer_macro_types(
name, ¶ms, interner, files, apidoc, fields_dict, rust_decl_dict, inline_fn_dict, typedefs,
&return_types_cache, ¶m_types_cache,
);
let is_confirmed = self.macros.get(&name)
.map(|info| info.get_return_type().is_some())
.unwrap_or(false);
if is_confirmed {
if let Some((macro_name, return_type)) = self.get_macro_return_type(name, interner) {
return_types_cache.insert(macro_name, return_type);
}
self.mark_confirmed(name);
self.cache_param_types_to(name, interner, &mut param_types_cache);
} else {
self.move_to_unknown(name);
}
}
break;
}
for name in candidates {
let params: Vec<InternedStr> = macro_table
.get(name)
.map(|def| match &def.kind {
MacroKind::Function { params, .. } => params.clone(),
MacroKind::Object => vec![],
})
.unwrap_or_default();
self.infer_macro_types(
name, ¶ms, interner, files, apidoc, fields_dict, rust_decl_dict, inline_fn_dict, typedefs,
&return_types_cache, ¶m_types_cache,
);
let is_confirmed = self.macros.get(&name)
.map(|info| {
info.get_return_type().is_some()
})
.unwrap_or(false);
if is_confirmed {
if let Some((macro_name, return_type)) = self.get_macro_return_type(name, interner) {
return_types_cache.insert(macro_name, return_type);
}
self.mark_confirmed(name);
self.cache_param_types_to(name, interner, &mut param_types_cache);
} else {
self.move_to_unknown(name);
}
}
}
self.macro_param_types = param_types_cache;
}
pub fn collect_uses_from_expr(
expr: &Expr,
uses: &mut HashSet<InternedStr>,
) {
match &expr.kind {
ExprKind::Call { func, args } => {
if let ExprKind::Ident(name) = &func.kind {
uses.insert(*name);
}
Self::collect_uses_from_expr(func, uses);
for arg in args {
Self::collect_uses_from_expr(arg, uses);
}
}
ExprKind::Ident(name) => {
uses.insert(*name);
}
ExprKind::Binary { lhs, rhs, .. } => {
Self::collect_uses_from_expr(lhs, uses);
Self::collect_uses_from_expr(rhs, uses);
}
ExprKind::Cast { expr: inner, .. }
| ExprKind::PreInc(inner)
| ExprKind::PreDec(inner)
| ExprKind::PostInc(inner)
| ExprKind::PostDec(inner)
| ExprKind::AddrOf(inner)
| ExprKind::Deref(inner)
| ExprKind::UnaryPlus(inner)
| ExprKind::UnaryMinus(inner)
| ExprKind::BitNot(inner)
| ExprKind::LogNot(inner)
| ExprKind::Sizeof(inner) => {
Self::collect_uses_from_expr(inner, uses);
}
ExprKind::Index { expr: base, index } => {
Self::collect_uses_from_expr(base, uses);
Self::collect_uses_from_expr(index, uses);
}
ExprKind::Member { expr: base, .. } | ExprKind::PtrMember { expr: base, .. } => {
Self::collect_uses_from_expr(base, uses);
}
ExprKind::Conditional { cond, then_expr, else_expr } => {
Self::collect_uses_from_expr(cond, uses);
Self::collect_uses_from_expr(then_expr, uses);
Self::collect_uses_from_expr(else_expr, uses);
}
ExprKind::Assign { lhs, rhs, .. } => {
Self::collect_uses_from_expr(lhs, uses);
Self::collect_uses_from_expr(rhs, uses);
}
ExprKind::Comma { lhs, rhs } => {
Self::collect_uses_from_expr(lhs, uses);
Self::collect_uses_from_expr(rhs, uses);
}
ExprKind::BuiltinCall { args, .. } => {
for arg in args {
if let crate::ast::BuiltinArg::Expr(e) = arg {
Self::collect_uses_from_expr(e, uses);
}
}
}
ExprKind::Assert { condition, .. } => {
Self::collect_uses_from_expr(condition, uses);
}
_ => {}
}
}
pub fn collect_function_calls_from_expr(
expr: &Expr,
calls: &mut HashSet<InternedStr>,
) {
match &expr.kind {
ExprKind::Call { func, args } => {
if let ExprKind::Ident(name) = &func.kind {
calls.insert(*name);
}
Self::collect_function_calls_from_expr(func, calls);
for arg in args {
Self::collect_function_calls_from_expr(arg, calls);
}
}
ExprKind::Binary { lhs, rhs, .. } => {
Self::collect_function_calls_from_expr(lhs, calls);
Self::collect_function_calls_from_expr(rhs, calls);
}
ExprKind::Cast { expr: inner, .. }
| ExprKind::PreInc(inner)
| ExprKind::PreDec(inner)
| ExprKind::PostInc(inner)
| ExprKind::PostDec(inner)
| ExprKind::AddrOf(inner)
| ExprKind::Deref(inner)
| ExprKind::UnaryPlus(inner)
| ExprKind::UnaryMinus(inner)
| ExprKind::BitNot(inner)
| ExprKind::LogNot(inner)
| ExprKind::Sizeof(inner) => {
Self::collect_function_calls_from_expr(inner, calls);
}
ExprKind::Index { expr: base, index } => {
Self::collect_function_calls_from_expr(base, calls);
Self::collect_function_calls_from_expr(index, calls);
}
ExprKind::Member { expr: base, .. } | ExprKind::PtrMember { expr: base, .. } => {
Self::collect_function_calls_from_expr(base, calls);
}
ExprKind::Conditional { cond, then_expr, else_expr } => {
Self::collect_function_calls_from_expr(cond, calls);
Self::collect_function_calls_from_expr(then_expr, calls);
Self::collect_function_calls_from_expr(else_expr, calls);
}
ExprKind::Assign { lhs, rhs, .. } => {
Self::collect_function_calls_from_expr(lhs, calls);
Self::collect_function_calls_from_expr(rhs, calls);
}
ExprKind::Comma { lhs, rhs } => {
Self::collect_function_calls_from_expr(lhs, calls);
Self::collect_function_calls_from_expr(rhs, calls);
}
ExprKind::StmtExpr(compound) => {
Self::collect_function_calls_from_block_items(&compound.items, calls);
}
ExprKind::BuiltinCall { args, .. } => {
for arg in args {
if let crate::ast::BuiltinArg::Expr(e) = arg {
Self::collect_function_calls_from_expr(e, calls);
}
}
}
ExprKind::Assert { condition, .. } => {
Self::collect_function_calls_from_expr(condition, calls);
}
_ => {}
}
}
pub fn collect_function_calls_from_block_items(
items: &[BlockItem],
calls: &mut HashSet<InternedStr>,
) {
for item in items {
match item {
BlockItem::Stmt(stmt) => {
Self::collect_function_calls_from_stmt(stmt, calls);
}
BlockItem::Decl(decl) => {
Self::collect_function_calls_from_decl(decl, calls);
}
}
}
}
fn collect_function_calls_from_decl(
decl: &crate::ast::Declaration,
calls: &mut HashSet<InternedStr>,
) {
for init_decl in &decl.declarators {
if let Some(init) = &init_decl.init {
Self::collect_function_calls_from_initializer(init, calls);
}
}
}
fn collect_function_calls_from_initializer(
init: &crate::ast::Initializer,
calls: &mut HashSet<InternedStr>,
) {
match init {
crate::ast::Initializer::Expr(expr) => {
Self::collect_function_calls_from_expr(expr, calls);
}
crate::ast::Initializer::List(items) => {
for item in items {
Self::collect_function_calls_from_initializer(&item.init, calls);
}
}
}
}
fn collect_function_calls_from_stmt(
stmt: &crate::ast::Stmt,
calls: &mut HashSet<InternedStr>,
) {
use crate::ast::{Stmt, ForInit};
match stmt {
Stmt::Expr(Some(expr), _) => {
Self::collect_function_calls_from_expr(expr, calls);
}
Stmt::If { cond, then_stmt, else_stmt, .. } => {
Self::collect_function_calls_from_expr(cond, calls);
Self::collect_function_calls_from_stmt(then_stmt, calls);
if let Some(else_s) = else_stmt {
Self::collect_function_calls_from_stmt(else_s, calls);
}
}
Stmt::While { cond, body, .. } => {
Self::collect_function_calls_from_expr(cond, calls);
Self::collect_function_calls_from_stmt(body, calls);
}
Stmt::DoWhile { body, cond, .. } => {
Self::collect_function_calls_from_stmt(body, calls);
Self::collect_function_calls_from_expr(cond, calls);
}
Stmt::For { init, cond, step, body, .. } => {
if let Some(for_init) = init {
match for_init {
ForInit::Expr(expr) => {
Self::collect_function_calls_from_expr(expr, calls);
}
ForInit::Decl(_) => {
}
}
}
if let Some(cond_expr) = cond {
Self::collect_function_calls_from_expr(cond_expr, calls);
}
if let Some(step_expr) = step {
Self::collect_function_calls_from_expr(step_expr, calls);
}
Self::collect_function_calls_from_stmt(body, calls);
}
Stmt::Compound(compound) => {
Self::collect_function_calls_from_block_items(&compound.items, calls);
}
Stmt::Return(Some(expr), _) => {
Self::collect_function_calls_from_expr(expr, calls);
}
Stmt::Switch { expr, body, .. } => {
Self::collect_function_calls_from_expr(expr, calls);
Self::collect_function_calls_from_stmt(body, calls);
}
Stmt::Label { stmt, .. } | Stmt::Case { stmt, .. } | Stmt::Default { stmt, .. } => {
Self::collect_function_calls_from_stmt(stmt, calls);
}
_ => {}
}
}
pub fn resolve_param_and_return_types(
&mut self,
interner: &mut StringInterner,
rust_decl_dict: Option<&crate::rust_decl::RustDeclDict>,
inline_fn_dict: &crate::inline_fn::InlineFnDict,
) {
self.propagate_macro_return_types(interner);
let sorted = self.topological_sort_for_resolve();
let mut callee_const_params: HashMap<InternedStr, HashSet<usize>> = HashMap::new();
Self::seed_callee_const(interner, rust_decl_dict, inline_fn_dict, &mut callee_const_params);
let mut bool_return_set: HashSet<InternedStr> = HashSet::new();
Self::seed_bool_returns(interner, rust_decl_dict, inline_fn_dict, &mut bool_return_set);
for name in &sorted {
let info = match self.macros.get(name) {
Some(info) => info,
None => continue,
};
if !info.is_parseable() || info.calls_unavailable || !info.is_function {
continue;
}
let must_mut = crate::rust_codegen::collect_must_mut_pointer_params(
&info.parse_result,
&info.params,
&callee_const_params,
);
let mut const_positions = HashSet::new();
for (i, param) in info.params.iter().enumerate() {
if !must_mut.contains(¶m.name) {
if Self::param_has_pointer_type_static(&info.type_env, param) {
const_positions.insert(i);
}
}
}
if !const_positions.is_empty() {
callee_const_params.insert(*name, const_positions.clone());
}
let is_bool = if let ParseResult::Expression(expr) = &info.parse_result {
crate::rust_codegen::is_boolean_expr_with_context(
expr, &bool_return_set, &bool_return_set,
)
} else {
false
};
if is_bool {
bool_return_set.insert(*name);
}
let info_mut = self.macros.get_mut(name).unwrap();
info_mut.const_pointer_positions = const_positions;
info_mut.is_bool_return = is_bool;
}
}
fn propagate_macro_return_types(&mut self, _interner: &StringInterner) {
let sorted = self.topological_sort_for_resolve();
let mut macro_returns: HashMap<InternedStr, TypeRepr> = HashMap::new();
for name in &sorted {
let computed: Option<(crate::ast::ExprId, TypeRepr)> = {
let info = match self.macros.get(name) {
Some(i) => i,
None => continue,
};
if !info.is_parseable() || info.calls_unavailable || !info.is_function {
continue;
}
let ParseResult::Expression(ref expr) = info.parse_result else {
continue;
};
compute_macro_return_type(expr, &info.type_env, ¯o_returns)
.map(|ty| (expr.id, ty))
};
if let Some((expr_id, ret_ty)) = computed {
macro_returns.insert(*name, ret_ty.clone());
let info_mut = self.macros.get_mut(name).unwrap();
info_mut.type_env.add_return_constraint(TypeConstraint::new(
expr_id,
ret_ty,
"macro return propagated from callee macros",
));
}
let updates: Vec<(crate::ast::ExprId, TypeRepr)> = {
let info = self.macros.get(name).unwrap();
let mut acc = Vec::new();
collect_macro_call_updates(&info.parse_result, ¯o_returns, &mut acc);
acc
};
if !updates.is_empty() {
let info_mut = self.macros.get_mut(name).unwrap();
for (eid, ty) in updates {
if !ty.is_concrete_pointer() {
continue;
}
if let Some(cs) = info_mut.type_env.expr_constraints.get_mut(&eid) {
let mut should_replace = false;
cs.retain(|c| {
let stale = c.context.starts_with("return type of macro ")
&& c.ty.is_void_pointer();
if stale {
should_replace = true;
false
} else {
true
}
});
if should_replace {
cs.push(TypeConstraint::new(
eid,
ty,
"return type from propagated callee macro (void* override)",
));
}
}
}
}
}
}
fn topological_sort_for_resolve(&self) -> Vec<InternedStr> {
use std::collections::VecDeque;
let target_macros: HashSet<InternedStr> = self.macros.iter()
.filter(|(_, info)| info.is_target && info.has_body && info.is_function)
.map(|(n, _)| *n)
.collect();
let mut in_degree: HashMap<InternedStr, usize> = HashMap::new();
for &name in &target_macros {
in_degree.entry(name).or_insert(0);
if let Some(info) = self.macros.get(&name) {
for used in &info.uses {
if target_macros.contains(used) {
*in_degree.entry(name).or_insert(0) += 1;
}
}
}
}
let mut queue: VecDeque<InternedStr> = in_degree.iter()
.filter(|(_, deg)| **deg == 0)
.map(|(&name, _)| name)
.collect();
let mut result = Vec::new();
while let Some(name) = queue.pop_front() {
result.push(name);
if let Some(info) = self.macros.get(&name) {
for user in &info.used_by {
if let Some(deg) = in_degree.get_mut(user) {
*deg = deg.saturating_sub(1);
if *deg == 0 {
queue.push_back(*user);
}
}
}
}
}
for &name in &target_macros {
if !result.contains(&name) {
result.push(name);
}
}
result
}
fn param_has_pointer_type_static(type_env: &crate::type_env::TypeEnv, param: &MacroParam) -> bool {
if let Some(expr_ids) = type_env.param_to_exprs.get(¶m.name) {
for expr_id in expr_ids {
if let Some(constraints) = type_env.expr_constraints.get(expr_id) {
for c in constraints {
if c.ty.has_outer_pointer() {
return true;
}
}
}
}
}
let expr_id = param.expr_id();
if let Some(constraints) = type_env.expr_constraints.get(&expr_id) {
for c in constraints {
if c.ty.has_outer_pointer() {
return true;
}
}
}
false
}
fn seed_callee_const(
interner: &mut StringInterner,
rust_decl_dict: Option<&crate::rust_decl::RustDeclDict>,
inline_fn_dict: &crate::inline_fn::InlineFnDict,
callee_const: &mut HashMap<InternedStr, HashSet<usize>>,
) {
if let Some(dict) = rust_decl_dict {
for (name, func) in &dict.fns {
let name_id = interner.intern(name);
let mut positions = HashSet::new();
for (i, param) in func.params.iter().enumerate() {
let normalized = param.ty.replace(" ", "");
if normalized.contains("*const") {
positions.insert(i);
}
}
if !positions.is_empty() {
callee_const.insert(name_id, positions);
}
}
}
for (name_id, fn_info) in inline_fn_dict.iter() {
let mut positions = HashSet::new();
for dd in &fn_info.declarator.derived {
if let crate::ast::DerivedDecl::Function(param_list) = dd {
for (i, param) in param_list.params.iter().enumerate() {
if let Some(ref decl) = param.declarator {
let has_const = decl.derived.iter().any(|d| {
matches!(d, crate::ast::DerivedDecl::Pointer(q) if q.is_const)
});
if has_const {
positions.insert(i);
}
}
}
break;
}
}
if !positions.is_empty() {
callee_const.insert(*name_id, positions);
}
}
}
fn seed_bool_returns(
interner: &mut StringInterner,
rust_decl_dict: Option<&crate::rust_decl::RustDeclDict>,
inline_fn_dict: &crate::inline_fn::InlineFnDict,
bool_returns: &mut HashSet<InternedStr>,
) {
if let Some(dict) = rust_decl_dict {
for (name, func) in &dict.fns {
if func.ret_ty.as_deref() == Some("bool") {
let name_id = interner.intern(name);
bool_returns.insert(name_id);
}
}
}
for (name_id, fn_info) in inline_fn_dict.iter() {
let has_bool = fn_info.specs.type_specs.iter()
.any(|ts| matches!(ts, crate::ast::TypeSpec::Bool));
if has_bool {
bool_returns.insert(*name_id);
}
}
}
}
impl Default for MacroInferContext {
fn default() -> Self {
Self::new()
}
}
fn collect_macro_call_updates(
parse_result: &ParseResult,
macro_returns: &HashMap<InternedStr, TypeRepr>,
acc: &mut Vec<(crate::ast::ExprId, TypeRepr)>,
) {
match parse_result {
ParseResult::Expression(e) => visit_expr_for_calls(e, macro_returns, acc),
ParseResult::Statement(items) => {
for it in items {
if let BlockItem::Stmt(stmt) = it {
visit_stmt_for_calls(stmt, macro_returns, acc);
}
}
}
ParseResult::Unparseable(_) => {}
}
}
fn visit_expr_for_calls(
expr: &Expr,
macro_returns: &HashMap<InternedStr, TypeRepr>,
acc: &mut Vec<(crate::ast::ExprId, TypeRepr)>,
) {
if let ExprKind::Call { func, args } = &expr.kind {
if let ExprKind::Ident(callee) = &func.kind {
if let Some(ty) = macro_returns.get(callee) {
acc.push((expr.id, ty.clone()));
}
}
visit_expr_for_calls(func, macro_returns, acc);
for a in args {
visit_expr_for_calls(a, macro_returns, acc);
}
return;
}
walk_expr_children(expr, &mut |e| visit_expr_for_calls(e, macro_returns, acc));
}
fn visit_stmt_for_calls(
stmt: &crate::ast::Stmt,
macro_returns: &HashMap<InternedStr, TypeRepr>,
acc: &mut Vec<(crate::ast::ExprId, TypeRepr)>,
) {
use crate::ast::Stmt;
match stmt {
Stmt::Compound(c) => {
for it in &c.items {
if let BlockItem::Stmt(s) = it {
visit_stmt_for_calls(s, macro_returns, acc);
}
}
}
Stmt::Expr(Some(e), _) | Stmt::Return(Some(e), _) => {
visit_expr_for_calls(e, macro_returns, acc)
}
Stmt::If { cond, then_stmt, else_stmt, .. } => {
visit_expr_for_calls(cond, macro_returns, acc);
visit_stmt_for_calls(then_stmt, macro_returns, acc);
if let Some(es) = else_stmt {
visit_stmt_for_calls(es, macro_returns, acc);
}
}
Stmt::While { cond, body, .. } | Stmt::DoWhile { body, cond, .. } => {
visit_expr_for_calls(cond, macro_returns, acc);
visit_stmt_for_calls(body, macro_returns, acc);
}
Stmt::For { init, cond, step, body, .. } => {
if let Some(crate::ast::ForInit::Expr(e)) = init {
visit_expr_for_calls(e, macro_returns, acc);
}
if let Some(c) = cond {
visit_expr_for_calls(c, macro_returns, acc);
}
if let Some(s) = step {
visit_expr_for_calls(s, macro_returns, acc);
}
visit_stmt_for_calls(body, macro_returns, acc);
}
Stmt::Switch { expr, body, .. } => {
visit_expr_for_calls(expr, macro_returns, acc);
visit_stmt_for_calls(body, macro_returns, acc);
}
Stmt::Case { expr, stmt, .. } => {
visit_expr_for_calls(expr, macro_returns, acc);
visit_stmt_for_calls(stmt, macro_returns, acc);
}
Stmt::Default { stmt, .. } | Stmt::Label { stmt, .. } => {
visit_stmt_for_calls(stmt, macro_returns, acc);
}
_ => {}
}
}
fn walk_expr_children<F: FnMut(&Expr)>(expr: &Expr, f: &mut F) {
match &expr.kind {
ExprKind::Ident(_)
| ExprKind::IntLit(_)
| ExprKind::UIntLit(_)
| ExprKind::FloatLit(_)
| ExprKind::CharLit(_)
| ExprKind::StringLit(_)
| ExprKind::SizeofType(_)
| ExprKind::Alignof(_) => {}
ExprKind::Call { func, args } => {
f(func);
for a in args { f(a); }
}
ExprKind::Index { expr: e, index } => { f(e); f(index); }
ExprKind::Member { expr: e, .. } | ExprKind::PtrMember { expr: e, .. } => f(e),
ExprKind::PostInc(e)
| ExprKind::PostDec(e)
| ExprKind::PreInc(e)
| ExprKind::PreDec(e)
| ExprKind::AddrOf(e)
| ExprKind::Deref(e)
| ExprKind::UnaryPlus(e)
| ExprKind::UnaryMinus(e)
| ExprKind::BitNot(e)
| ExprKind::LogNot(e)
| ExprKind::Sizeof(e) => f(e),
ExprKind::Cast { expr: e, .. } => f(e),
ExprKind::Binary { lhs, rhs, .. } => { f(lhs); f(rhs); }
ExprKind::Assign { lhs, rhs, .. } => { f(lhs); f(rhs); }
ExprKind::Conditional { cond, then_expr, else_expr } => {
f(cond); f(then_expr); f(else_expr);
}
ExprKind::Comma { lhs, rhs } => { f(lhs); f(rhs); }
ExprKind::CompoundLit { .. } => {}
ExprKind::BuiltinCall { args, .. } => {
for a in args {
if let crate::ast::BuiltinArg::Expr(e) = a { f(e); }
}
}
ExprKind::StmtExpr(_) => {}
ExprKind::Assert { condition, .. } => f(condition),
ExprKind::MacroCall { args, expanded, .. } => {
for a in args { f(a); }
f(expanded);
}
}
}
fn compute_macro_return_type(
expr: &Expr,
env: &TypeEnv,
macro_returns: &HashMap<InternedStr, TypeRepr>,
) -> Option<TypeRepr> {
match &expr.kind {
ExprKind::Conditional { then_expr, else_expr, .. } => {
let then_ty = compute_macro_return_type(then_expr, env, macro_returns)
.or_else(|| existing_constraint_type(then_expr.id, env));
let else_ty = compute_macro_return_type(else_expr, env, macro_returns)
.or_else(|| existing_constraint_type(else_expr.id, env));
resolve_conditional_branches(then_ty, else_ty)
}
ExprKind::Call { func, .. } => {
if let ExprKind::Ident(callee_name) = &func.kind {
if let Some(ty) = macro_returns.get(callee_name) {
return Some(ty.clone());
}
}
existing_constraint_type(expr.id, env)
}
ExprKind::Comma { rhs, .. } => {
compute_macro_return_type(rhs, env, macro_returns)
.or_else(|| existing_constraint_type(rhs.id, env))
}
ExprKind::Binary { op, lhs, rhs } => {
let lhs_ty = compute_macro_return_type(lhs, env, macro_returns)
.or_else(|| existing_constraint_type(lhs.id, env));
let rhs_ty = compute_macro_return_type(rhs, env, macro_returns)
.or_else(|| existing_constraint_type(rhs.id, env));
resolve_binary(op, lhs_ty, rhs_ty)
}
ExprKind::Cast { .. } => existing_constraint_type(expr.id, env),
_ => existing_constraint_type(expr.id, env),
}
}
fn existing_constraint_type(
expr_id: crate::ast::ExprId,
env: &TypeEnv,
) -> Option<TypeRepr> {
env.expr_constraints
.get(&expr_id)
.and_then(|cs| cs.first())
.map(|c| c.ty.clone())
}
fn resolve_binary(
op: &crate::ast::BinOp,
lhs_ty: Option<TypeRepr>,
rhs_ty: Option<TypeRepr>,
) -> Option<TypeRepr> {
use crate::ast::BinOp;
match op {
BinOp::Add | BinOp::Sub => {
let lhs_is_ptr = lhs_ty.as_ref().is_some_and(|t| t.is_pointer_type());
let rhs_is_ptr = rhs_ty.as_ref().is_some_and(|t| t.is_pointer_type());
match (lhs_is_ptr, rhs_is_ptr) {
(true, false) => lhs_ty,
(false, true) => rhs_ty,
_ => None, }
}
_ => None,
}
}
fn resolve_conditional_branches(
then_ty: Option<TypeRepr>,
else_ty: Option<TypeRepr>,
) -> Option<TypeRepr> {
match (&then_ty, &else_ty) {
(Some(t), Some(e)) => {
if t.is_void_pointer() && e.is_concrete_pointer() {
return else_ty;
}
if e.is_void_pointer() && t.is_concrete_pointer() {
return then_ty;
}
then_ty
}
(Some(_), None) => then_ty,
(None, Some(_)) => else_ty,
(None, None) => None,
}
}
fn inject_comma_after_assert_underscore(
tokens: &[Token],
no_expand: &NoExpandSymbols,
) -> Vec<Token> {
let assert_underscore = no_expand.assert_;
let mut result = Vec::with_capacity(tokens.len());
let mut i = 0;
while i < tokens.len() {
if matches!(tokens[i].kind, TokenKind::Ident(name) if name == assert_underscore) {
result.push(tokens[i].clone());
i += 1;
while i < tokens.len() && matches!(tokens[i].kind, TokenKind::Space | TokenKind::Newline) {
result.push(tokens[i].clone());
i += 1;
}
if i < tokens.len() && matches!(tokens[i].kind, TokenKind::LParen) {
let mut depth = 0;
loop {
if i >= tokens.len() {
break;
}
match tokens[i].kind {
TokenKind::LParen => depth += 1,
TokenKind::RParen => {
depth -= 1;
if depth == 0 {
result.push(tokens[i].clone());
i += 1;
break;
}
}
_ => {}
}
result.push(tokens[i].clone());
i += 1;
}
let next_significant = tokens[i..].iter()
.find(|t| !matches!(t.kind, TokenKind::Space | TokenKind::Newline));
let needs_comma = next_significant
.is_some_and(|t| !matches!(t.kind,
TokenKind::Comma | TokenKind::RParen | TokenKind::Eof
| TokenKind::Semi));
if needs_comma {
let loc = result.last().map(|t| t.loc.clone())
.unwrap_or_default();
result.push(Token::new(TokenKind::Comma, loc));
}
}
} else {
result.push(tokens[i].clone());
i += 1;
}
}
result
}
pub fn detect_assert_kind(name: &str) -> Option<AssertKind> {
match name {
"assert" => Some(AssertKind::Assert),
"assert_" => Some(AssertKind::AssertUnderscore),
_ => None,
}
}
pub fn convert_assert_calls(expr: &mut Expr, interner: &StringInterner) {
match &mut expr.kind {
ExprKind::Call { func, args } => {
convert_assert_calls(func, interner);
for arg in args.iter_mut() {
convert_assert_calls(arg, interner);
}
if let ExprKind::Ident(name) = &func.kind {
let name_str = interner.get(*name);
if let Some(kind) = detect_assert_kind(name_str) {
if let Some(condition) = args.pop() {
expr.kind = ExprKind::Assert {
kind,
condition: Box::new(condition),
};
}
}
}
}
ExprKind::Binary { lhs, rhs, .. } => {
convert_assert_calls(lhs, interner);
convert_assert_calls(rhs, interner);
}
ExprKind::Cast { expr: inner, .. }
| ExprKind::PreInc(inner)
| ExprKind::PreDec(inner)
| ExprKind::PostInc(inner)
| ExprKind::PostDec(inner)
| ExprKind::AddrOf(inner)
| ExprKind::Deref(inner)
| ExprKind::UnaryPlus(inner)
| ExprKind::UnaryMinus(inner)
| ExprKind::BitNot(inner)
| ExprKind::LogNot(inner)
| ExprKind::Sizeof(inner) => {
convert_assert_calls(inner, interner);
}
ExprKind::Index { expr: base, index } => {
convert_assert_calls(base, interner);
convert_assert_calls(index, interner);
}
ExprKind::Member { expr: base, .. } | ExprKind::PtrMember { expr: base, .. } => {
convert_assert_calls(base, interner);
}
ExprKind::Conditional { cond, then_expr, else_expr } => {
convert_assert_calls(cond, interner);
convert_assert_calls(then_expr, interner);
convert_assert_calls(else_expr, interner);
}
ExprKind::Assign { lhs, rhs, .. } => {
convert_assert_calls(lhs, interner);
convert_assert_calls(rhs, interner);
}
ExprKind::Comma { lhs, rhs } => {
convert_assert_calls(lhs, interner);
convert_assert_calls(rhs, interner);
}
ExprKind::Assert { condition, .. } => {
convert_assert_calls(condition, interner);
}
ExprKind::CompoundLit { init, .. } => {
for item in init {
if let crate::ast::Initializer::Expr(e) = &mut item.init {
convert_assert_calls(e, interner);
}
}
}
ExprKind::StmtExpr(compound) => {
for item in &mut compound.items {
if let BlockItem::Stmt(stmt) = item {
convert_assert_calls_in_stmt(stmt, interner);
}
}
}
ExprKind::MacroCall { args, expanded, .. } => {
for arg in args.iter_mut() {
convert_assert_calls(arg, interner);
}
convert_assert_calls(expanded, interner);
}
ExprKind::BuiltinCall { args, .. } => {
for arg in args.iter_mut() {
if let crate::ast::BuiltinArg::Expr(e) = arg {
convert_assert_calls(e, interner);
}
}
}
ExprKind::Ident(_)
| ExprKind::IntLit(_)
| ExprKind::UIntLit(_)
| ExprKind::FloatLit(_)
| ExprKind::CharLit(_)
| ExprKind::StringLit(_)
| ExprKind::SizeofType(_)
| ExprKind::Alignof(_) => {}
}
}
pub fn convert_assert_calls_in_compound_stmt(compound: &mut crate::ast::CompoundStmt, interner: &StringInterner) {
use crate::ast::BlockItem;
for item in &mut compound.items {
if let BlockItem::Stmt(s) = item {
convert_assert_calls_in_stmt(s, interner);
}
}
}
pub fn convert_assert_calls_in_stmt(stmt: &mut crate::ast::Stmt, interner: &StringInterner) {
use crate::ast::Stmt;
match stmt {
Stmt::Expr(Some(expr), _) => convert_assert_calls(expr, interner),
Stmt::If { cond, then_stmt, else_stmt, .. } => {
convert_assert_calls(cond, interner);
convert_assert_calls_in_stmt(then_stmt, interner);
if let Some(else_s) = else_stmt {
convert_assert_calls_in_stmt(else_s, interner);
}
}
Stmt::While { cond, body, .. } => {
convert_assert_calls(cond, interner);
convert_assert_calls_in_stmt(body, interner);
}
Stmt::DoWhile { body, cond, .. } => {
convert_assert_calls_in_stmt(body, interner);
convert_assert_calls(cond, interner);
}
Stmt::For { init, cond, step, body, .. } => {
if let Some(crate::ast::ForInit::Expr(e)) = init {
convert_assert_calls(e, interner);
}
if let Some(c) = cond {
convert_assert_calls(c, interner);
}
if let Some(s) = step {
convert_assert_calls(s, interner);
}
convert_assert_calls_in_stmt(body, interner);
}
Stmt::Switch { expr, body, .. } => {
convert_assert_calls(expr, interner);
convert_assert_calls_in_stmt(body, interner);
}
Stmt::Return(Some(expr), _) => convert_assert_calls(expr, interner),
Stmt::Compound(compound) => {
for item in &mut compound.items {
match item {
BlockItem::Stmt(s) => convert_assert_calls_in_stmt(s, interner),
BlockItem::Decl(_) => {}
}
}
}
Stmt::Label { stmt: s, .. }
| Stmt::Case { stmt: s, .. }
| Stmt::Default { stmt: s, .. } => {
convert_assert_calls_in_stmt(s, interner);
}
_ => {}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MacroInferStats {
pub total: usize,
pub confirmed: usize,
pub unconfirmed: usize,
pub args_unknown: usize,
pub return_unknown: usize,
}
impl std::fmt::Display for MacroInferStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MacroInferStats {{ total: {}, confirmed: {}, unconfirmed: {}, args_unknown: {}, return_unknown: {} }}",
self.total, self.confirmed, self.unconfirmed, self.args_unknown, self.return_unknown
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::intern::StringInterner;
#[test]
fn test_macro_infer_info_new() {
let mut interner = StringInterner::new();
let name = interner.intern("MY_MACRO");
let info = MacroInferInfo::new(name);
assert_eq!(info.name, name);
assert!(!info.is_target);
assert!(!info.is_thx_dependent);
assert!(!info.has_token_pasting);
assert!(info.uses.is_empty());
assert!(info.used_by.is_empty());
assert!(!info.is_parseable());
assert_eq!(info.args_infer_status, InferStatus::Pending);
assert_eq!(info.return_infer_status, InferStatus::Pending);
}
#[test]
fn test_macro_infer_context_register() {
let mut interner = StringInterner::new();
let name = interner.intern("FOO");
let mut ctx = MacroInferContext::new();
let info = MacroInferInfo::new(name);
ctx.register(info);
assert!(ctx.get(name).is_some());
assert_eq!(ctx.macros.len(), 1);
}
#[test]
fn test_build_use_relations() {
let mut interner = StringInterner::new();
let foo = interner.intern("FOO");
let bar = interner.intern("BAR");
let baz = interner.intern("BAZ");
let mut ctx = MacroInferContext::new();
let mut foo_info = MacroInferInfo::new(foo);
foo_info.add_use(bar);
ctx.register(foo_info);
let mut bar_info = MacroInferInfo::new(bar);
bar_info.add_use(baz);
ctx.register(bar_info);
let baz_info = MacroInferInfo::new(baz);
ctx.register(baz_info);
ctx.build_use_relations();
assert!(ctx.get(bar).unwrap().used_by.contains(&foo));
assert!(ctx.get(baz).unwrap().used_by.contains(&bar));
}
#[test]
fn test_inference_candidates() {
let mut interner = StringInterner::new();
let foo = interner.intern("FOO");
let bar = interner.intern("BAR");
let baz = interner.intern("BAZ");
let mut ctx = MacroInferContext::new();
let mut foo_info = MacroInferInfo::new(foo);
foo_info.add_use(bar);
ctx.register(foo_info);
let mut bar_info = MacroInferInfo::new(bar);
bar_info.add_use(baz);
ctx.register(bar_info);
let mut baz_info = MacroInferInfo::new(baz);
baz_info.args_infer_status = InferStatus::TypeComplete;
baz_info.return_infer_status = InferStatus::TypeComplete;
ctx.register(baz_info);
ctx.classify_initial();
assert!(ctx.confirmed.contains(&baz));
assert!(ctx.unconfirmed.contains(&foo));
assert!(ctx.unconfirmed.contains(&bar));
let candidates = ctx.get_inference_candidates();
assert_eq!(candidates, vec![bar]);
ctx.mark_confirmed(bar);
let candidates = ctx.get_inference_candidates();
assert_eq!(candidates, vec![foo]);
}
#[test]
fn test_no_expand_symbols_new() {
let mut interner = StringInterner::new();
let symbols = NoExpandSymbols::new(&mut interner);
assert_eq!(interner.get(symbols.assert), "assert");
assert_eq!(interner.get(symbols.assert_), "assert_");
}
#[test]
fn test_no_expand_symbols_iter() {
let mut interner = StringInterner::new();
let symbols = NoExpandSymbols::new(&mut interner);
let syms: Vec<_> = symbols.iter().collect();
assert_eq!(syms.len(), 2);
assert!(syms.contains(&symbols.assert));
assert!(syms.contains(&symbols.assert_));
}
#[test]
fn test_explicit_expand_symbols_new() {
let mut interner = StringInterner::new();
let symbols = ExplicitExpandSymbols::new(&mut interner);
assert_eq!(interner.get(symbols.sv_any), "SvANY");
assert_eq!(interner.get(symbols.sv_flags), "SvFLAGS");
assert_eq!(interner.get(symbols.expect), "EXPECT");
assert_eq!(interner.get(symbols.likely), "LIKELY");
assert_eq!(interner.get(symbols.unlikely), "UNLIKELY");
assert_eq!(interner.get(symbols.cbool), "cBOOL");
assert_eq!(interner.get(symbols.assert_underscore_), "__ASSERT_");
assert_eq!(interner.get(symbols.str_with_len), "STR_WITH_LEN");
assert_eq!(interner.get(symbols.assert_not_rok), "assert_not_ROK");
assert_eq!(interner.get(symbols.assert_not_glob), "assert_not_glob");
assert_eq!(interner.get(symbols.mutable_ptr), "MUTABLE_PTR");
}
#[test]
fn test_explicit_expand_symbols_iter() {
let mut interner = StringInterner::new();
let symbols = ExplicitExpandSymbols::new(&mut interner);
let syms: Vec<_> = symbols.iter().collect();
assert_eq!(syms.len(), 14);
assert!(syms.contains(&symbols.sv_any));
assert!(syms.contains(&symbols.sv_flags));
assert!(syms.contains(&symbols.cv_flags));
assert!(syms.contains(&symbols.hek_flags));
assert!(syms.contains(&symbols.expect));
assert!(syms.contains(&symbols.likely));
assert!(syms.contains(&symbols.unlikely));
assert!(syms.contains(&symbols.cbool));
assert!(syms.contains(&symbols.assert_underscore_));
assert!(syms.contains(&symbols.str_with_len));
assert!(syms.contains(&symbols.assert_not_rok));
assert!(syms.contains(&symbols.assert_not_glob));
assert!(syms.contains(&symbols.mutable_ptr));
}
}