Extract annotations from an annotate function's bytecode. The annotate function uses BUILD_MAP with key-value pairs loaded before it. Keys are parameter names (from LOAD_CONST), values are type names (from LOAD_NAME/LOAD_GLOBAL).
(code: &CodeObject)
| 78 | /// The annotate function uses BUILD_MAP with key-value pairs loaded before it. |
| 79 | /// Keys are parameter names (from LOAD_CONST), values are type names (from LOAD_NAME/LOAD_GLOBAL). |
| 80 | fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap<Wtf8Buf, StackValue> { |
| 81 | let mut annotations = HashMap::new(); |
| 82 | let mut stack: Vec<(bool, usize)> = Vec::new(); // (is_const, index) |
| 83 | let mut op_arg_state = OpArgState::default(); |
| 84 | |
| 85 | for &word in code.instructions.iter() { |
| 86 | let (instruction, arg) = op_arg_state.get(word); |
| 87 | |
| 88 | match instruction { |
| 89 | Instruction::LoadConst { consti } => { |
| 90 | stack.push((true, consti.get(arg).as_usize())); |
| 91 | } |
| 92 | Instruction::LoadName { namei } => { |
| 93 | stack.push((false, namei.get(arg) as usize)); |
| 94 | } |
| 95 | Instruction::LoadGlobal { namei } => { |
| 96 | stack.push((false, (namei.get(arg) >> 1) as usize)); |
| 97 | } |
| 98 | Instruction::BuildMap { count } => { |
| 99 | let count = count.get(arg) as usize; |
| 100 | // Stack has key-value pairs in order: k1, v1, k2, v2, ... |
| 101 | // So we need count * 2 items from the stack |
| 102 | let start = stack.len().saturating_sub(count * 2); |
| 103 | let pairs: Vec<_> = stack.drain(start..).collect(); |
| 104 | |
| 105 | for chunk in pairs.chunks(2) { |
| 106 | if chunk.len() == 2 { |
| 107 | let (key_is_const, key_idx) = chunk[0]; |
| 108 | let (val_is_const, val_idx) = chunk[1]; |
| 109 | |
| 110 | // Key should be a const string (parameter name) |
| 111 | if key_is_const |
| 112 | && let ConstantData::Str { value } = |
| 113 | &code.constants[(key_idx as u32).into()] |
| 114 | { |
| 115 | let param_name = value; |
| 116 | // Value can be a name (type ref) or a const string (forward ref) |
| 117 | let type_name = if val_is_const { |
| 118 | match code.constants.get(val_idx) { |
| 119 | Some(ConstantData::Str { value }) => value |
| 120 | .as_str() |
| 121 | .map(|s| s.to_owned()) |
| 122 | .unwrap_or_else(|_| value.to_string_lossy().into_owned()), |
| 123 | Some(other) => panic!( |
| 124 | "Unsupported annotation const for '{:?}' at idx {}: {:?}", |
| 125 | param_name, val_idx, other |
| 126 | ), |
| 127 | None => panic!( |
| 128 | "Annotation const idx out of bounds for '{:?}': {} (len={})", |
| 129 | param_name, |
| 130 | val_idx, |
| 131 | code.constants.len() |
| 132 | ), |
| 133 | } |
| 134 | } else { |
| 135 | match code.names.get(val_idx) { |
| 136 | Some(name) => name.clone(), |
| 137 | None => panic!( |
no test coverage detected