rue_compiler/compile/
field.rs

1use log::debug;
2use rue_diagnostic::DiagnosticKind;
3use rue_hir::{Hir, TypePath, UnaryOp, Value};
4use rue_parser::SyntaxToken;
5use rue_types::{Type, Union};
6
7use crate::{Compiler, GetTextRange};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum Field<'a> {
11    Named(&'a str),
12    First,
13    Rest,
14}
15
16#[derive(Debug, Clone)]
17pub enum FieldResult {
18    Value(Value),
19    Unknown,
20    Error,
21}
22
23pub fn compile_named_field(ctx: &mut Compiler, expr: &Value, name: &SyntaxToken) -> Value {
24    match compile_field(ctx, expr.clone(), &Field::Named(name.text())) {
25        FieldResult::Value(value) => value,
26        FieldResult::Unknown => {
27            debug!("Unresolved field access due to unknown field");
28            let type_name = ctx.type_name(expr.ty);
29            ctx.diagnostic(
30                name,
31                DiagnosticKind::UnknownField(name.text().to_string(), type_name),
32            );
33            ctx.builtins().unresolved.clone()
34        }
35        FieldResult::Error => {
36            debug!("Unresolved field access due to missing field in underlying struct type");
37            let type_name = ctx.type_name(expr.ty);
38            ctx.diagnostic(
39                name,
40                DiagnosticKind::MissingField(name.text().to_string(), type_name),
41            );
42            ctx.builtins().unresolved.clone()
43        }
44    }
45}
46
47pub fn compile_pair_fields(
48    ctx: &mut Compiler,
49    node: &impl GetTextRange,
50    expr: &Value,
51) -> (Value, Value) {
52    let first = compile_field(ctx, expr.clone(), &Field::First);
53    let rest = compile_field(ctx, expr.clone(), &Field::Rest);
54
55    let (FieldResult::Value(first), FieldResult::Value(rest)) = (first, rest) else {
56        let name = ctx.type_name(expr.ty);
57        ctx.diagnostic(node, DiagnosticKind::CannotDestructurePair(name));
58        return (
59            ctx.builtins().unresolved.clone(),
60            ctx.builtins().unresolved.clone(),
61        );
62    };
63
64    (first, rest)
65}
66
67pub fn compile_field(ctx: &mut Compiler, expr: Value, name: &Field<'_>) -> FieldResult {
68    let ty = rue_types::unwrap_semantic(ctx.types_mut(), expr.ty, true);
69
70    match ctx.ty(ty).clone() {
71        Type::Apply(_) | Type::Alias(_) | Type::Ref(_) => unreachable!(),
72        Type::Unresolved => FieldResult::Unknown,
73        Type::Generic(_) | Type::Function(_) | Type::Never | Type::Any => FieldResult::Unknown,
74        Type::Atom(_) => {
75            if let &Field::Named(name) = name
76                && name == "length"
77            {
78                let hir = ctx.alloc_hir(Hir::Unary(UnaryOp::Strlen, expr.hir));
79                FieldResult::Value(Value::new(hir, ctx.builtins().types.int))
80            } else {
81                FieldResult::Unknown
82            }
83        }
84        Type::Pair(_) | Type::Union(_) => {
85            let pairs = rue_types::extract_pairs(ctx.types_mut(), ty, true);
86
87            match name {
88                Field::Named("first") | Field::First if !pairs.is_empty() => {
89                    let hir = ctx.alloc_hir(Hir::Unary(UnaryOp::First, expr.hir));
90
91                    let first = if pairs.len() == 1 {
92                        pairs[0].first
93                    } else {
94                        ctx.alloc_type(Type::Union(Union::new(
95                            pairs.iter().map(|pair| pair.first).collect(),
96                        )))
97                    };
98
99                    let mut value = Value::new(hir, first);
100
101                    if let Some(mut reference) = expr.reference {
102                        reference.path.push(TypePath::First);
103                        value = value.with_reference(reference);
104                    }
105
106                    FieldResult::Value(value)
107                }
108                Field::Named("rest") | Field::Rest if !pairs.is_empty() => {
109                    let hir = ctx.alloc_hir(Hir::Unary(UnaryOp::Rest, expr.hir));
110
111                    let rest = if pairs.len() == 1 {
112                        pairs[0].rest
113                    } else {
114                        ctx.alloc_type(Type::Union(Union::new(
115                            pairs.iter().map(|pair| pair.rest).collect(),
116                        )))
117                    };
118
119                    let mut value = Value::new(hir, rest);
120
121                    if let Some(mut reference) = expr.reference {
122                        reference.path.push(TypePath::Rest);
123                        value = value.with_reference(reference);
124                    }
125
126                    FieldResult::Value(value)
127                }
128                _ => FieldResult::Unknown,
129            }
130        }
131        Type::Struct(ty) => {
132            let &Field::Named(name) = name else {
133                return FieldResult::Unknown;
134            };
135
136            let Some(index) = ty.fields.get_index_of(name) else {
137                return FieldResult::Unknown;
138            };
139
140            let mut hir = expr.hir;
141            let mut field_type = ty.inner;
142            let mut reference = expr.reference;
143
144            let needs_first = index + 1 < ty.fields.len() || ty.nil_terminated;
145
146            for i in 0..index {
147                hir = ctx.alloc_hir(Hir::Unary(UnaryOp::Rest, hir));
148
149                let pairs = rue_types::extract_pairs(ctx.types_mut(), field_type, true);
150
151                if pairs.is_empty() || (pairs.len() > 1 && (i + 1 < index || needs_first)) {
152                    return FieldResult::Error;
153                }
154
155                field_type = if pairs.len() == 1 {
156                    pairs[0].rest
157                } else {
158                    ctx.alloc_type(Type::Union(Union::new(
159                        pairs.into_iter().map(|pair| pair.rest).collect(),
160                    )))
161                };
162
163                if let Some(reference) = reference.as_mut() {
164                    reference.path.push(TypePath::Rest);
165                }
166            }
167
168            if needs_first {
169                hir = ctx.alloc_hir(Hir::Unary(UnaryOp::First, hir));
170
171                let pairs = rue_types::extract_pairs(ctx.types_mut(), field_type, true);
172
173                if pairs.is_empty() {
174                    return FieldResult::Error;
175                }
176
177                field_type = if pairs.len() == 1 {
178                    pairs[0].first
179                } else {
180                    ctx.alloc_type(Type::Union(Union::new(
181                        pairs.into_iter().map(|pair| pair.first).collect(),
182                    )))
183                };
184
185                if let Some(reference) = reference.as_mut() {
186                    reference.path.push(TypePath::First);
187                }
188            }
189
190            let mut value = Value::new(hir, field_type);
191
192            if let Some(reference) = reference {
193                value = value.with_reference(reference);
194            }
195
196            FieldResult::Value(value)
197        }
198    }
199}