rue_compiler/compile/
field.rs

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