1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Types lowered at compile time into a plain IR, so chunks hold no syn AST.
//! syn nodes are not `Send`, so this lowering is what lets the same cast,
//! coercion, and turbofish tables serve both engines, and it moves all name
//! resolution to load time, out of the hot runtime paths.
use std::rc::Rc;
use std::sync::Arc;
use super::numeric::IntWidth;
use super::resolver::{Res, Resolver};
/// Target of an `as` cast, reduced to what the engines act on.
#[derive(Clone)]
pub enum CastIr {
F64,
F32,
Char,
Int(IntWidth),
/// A target with no runtime semantics. Kept so the cast fails only if it
/// actually runs, the way it always did, since dead code may hold one.
Unsupported(Arc<str>),
}
pub fn lower_cast(ty: &syn::Type) -> CastIr {
let name = match ty {
syn::Type::Path(p) => p
.path
.segments
.last()
.map(|s| s.ident.to_string())
.unwrap_or_default(),
_ => String::new(),
};
match name.as_str() {
"f64" => CastIr::F64,
"f32" => CastIr::F32,
"char" => CastIr::Char,
// u128 and i128 carry no runtime width yet and keep the old
// i64-passthrough.
"u128" | "i128" => CastIr::Int(IntWidth::I64),
_ => match IntWidth::parse(&name) {
Some(w) => CastIr::Int(w),
None => CastIr::Unsupported(Arc::from(name.as_str())),
},
}
}
/// A type annotation or turbofish, lowered against the module it was written
/// in. Aliases are followed and struct paths canonicalized here, so runtime
/// never resolves a name.
#[derive(Clone)]
pub enum TypeIr {
/// A type coercion cannot change, parsed dynamically by typed json.
Dynamic,
/// `Vec<T>` or `VecDeque<T>`.
Vec(Arc<TypeIr>),
/// The value type of `HashMap<K, V>` or `BTreeMap<K, V>`. Coercion leaves
/// maps untouched, typed json uses it for the entry values.
MapValue(Arc<TypeIr>),
Option(Arc<TypeIr>),
/// A user struct, by canonical name.
Struct(Arc<str>),
/// A generic parameter of the enclosing function, bound to a concrete
/// type by the caller's turbofish through the type environment.
Generic(Arc<str>),
}
impl TypeIr {
/// Whether coercing a value through this type can ever change it.
pub fn is_active(&self) -> bool {
match self {
TypeIr::Dynamic | TypeIr::Generic(_) | TypeIr::MapValue(_) => false,
TypeIr::Struct(_) => true,
TypeIr::Vec(inner) | TypeIr::Option(inner) => inner.is_active(),
}
}
}
/// Bound on alias chains, so a `type A = B; type B = A;` cycle lowers to
/// `Dynamic` instead of hanging the compiler.
const MAX_DEPTH: u32 = 32;
/// `generics` are the type parameter names of the function being compiled. A
/// bare parameter name shadows any type of the same name, as in real Rust.
pub fn lower_type(
ty: &syn::Type,
resolver: &Resolver,
module: usize,
generics: &[Rc<str>],
) -> TypeIr {
lower(ty, resolver, module, generics, 0)
}
fn lower(
ty: &syn::Type,
resolver: &Resolver,
module: usize,
generics: &[Rc<str>],
depth: u32,
) -> TypeIr {
if depth > MAX_DEPTH {
return TypeIr::Dynamic;
}
let syn::Type::Path(p) = ty else {
return TypeIr::Dynamic;
};
if p.qself.is_none()
&& p.path.segments.len() == 1
&& matches!(p.path.segments[0].arguments, syn::PathArguments::None)
&& let Some(g) = generics.iter().find(|g| p.path.segments[0].ident == ***g)
{
return TypeIr::Generic(Arc::from(&**g));
}
let Some(seg) = p.path.segments.last() else {
return TypeIr::Dynamic;
};
let name = seg.ident.to_string();
let arg = |i: usize| {
type_arg(seg, i).map(|t| Arc::new(lower(t, resolver, module, generics, depth + 1)))
};
match name.as_str() {
"Vec" | "VecDeque" => arg(0).map(TypeIr::Vec).unwrap_or(TypeIr::Dynamic),
"Option" => arg(0).map(TypeIr::Option).unwrap_or(TypeIr::Dynamic),
// Smart pointers are transparent at runtime.
"Box" | "Rc" | "Arc" => match type_arg(seg, 0) {
Some(t) => lower(t, resolver, module, generics, depth + 1),
None => TypeIr::Dynamic,
},
"HashMap" | "BTreeMap" => arg(1).map(TypeIr::MapValue).unwrap_or(TypeIr::Dynamic),
_ => {
if let Some(canon) = resolver.resolve_struct_key(module, &p.path) {
return TypeIr::Struct(Arc::from(&*canon));
}
let segs: Vec<String> = p
.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect();
match resolver.resolve(module, &segs) {
// An alias target resolves in the module the alias was
// declared in, where no function generics apply.
Ok(Res::Alias(m, target)) => lower(&target, resolver, m, &[], depth + 1),
_ => TypeIr::Dynamic,
}
}
}
}
/// The `i`-th type argument of a segment, `HashMap<K, V>` at 1 gives `V`.
fn type_arg(seg: &syn::PathSegment, i: usize) -> Option<&syn::Type> {
match &seg.arguments {
syn::PathArguments::AngleBracketed(a) => a
.args
.iter()
.filter_map(|g| match g {
syn::GenericArgument::Type(t) => Some(t),
_ => None,
})
.nth(i),
_ => None,
}
}