starlark/eval/compiler/
expr.rs

1/*
2 * Copyright 2018 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18//! Evaluation of an expression.
19
20use std::borrow::Cow;
21use std::cmp::Ordering;
22
23use dupe::Dupe;
24use starlark_derive::VisitSpanMut;
25use starlark_syntax::slice_vec_ext::SliceExt;
26use starlark_syntax::syntax::ast::AstExprP;
27use starlark_syntax::syntax::ast::AstLiteral;
28use starlark_syntax::syntax::ast::AstPayload;
29use starlark_syntax::syntax::ast::BinOp;
30use starlark_syntax::syntax::ast::ExprP;
31use starlark_syntax::syntax::ast::FStringP;
32use starlark_syntax::syntax::ast::LambdaP;
33use starlark_syntax::syntax::ast::StmtP;
34use thiserror::Error;
35
36use crate::codemap::Spanned;
37use crate::collections::symbol::symbol::Symbol;
38use crate::environment::slots::ModuleSlotId;
39use crate::errors::did_you_mean::did_you_mean;
40use crate::eval::compiler::args::ArgsCompiledValue;
41use crate::eval::compiler::call::CallCompiled;
42use crate::eval::compiler::compr::ComprCompiled;
43use crate::eval::compiler::constants::Constants;
44use crate::eval::compiler::def::DefCompiled;
45use crate::eval::compiler::def::FrozenDef;
46use crate::eval::compiler::error::CompilerInternalError;
47use crate::eval::compiler::expr_bool::ExprCompiledBool;
48use crate::eval::compiler::known::list_to_tuple;
49use crate::eval::compiler::opt_ctx::OptCtx;
50use crate::eval::compiler::scope::payload::CstExpr;
51use crate::eval::compiler::scope::payload::CstIdent;
52use crate::eval::compiler::scope::AssignCount;
53use crate::eval::compiler::scope::Captured;
54use crate::eval::compiler::scope::ResolvedIdent;
55use crate::eval::compiler::scope::Slot;
56use crate::eval::compiler::span::IrSpanned;
57use crate::eval::compiler::Compiler;
58use crate::eval::runtime::frame_span::FrameSpan;
59use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
60use crate::eval::runtime::slots::LocalCapturedSlotId;
61use crate::eval::runtime::slots::LocalSlotId;
62use crate::eval::Arguments;
63use crate::eval::Evaluator;
64use crate::values::bool::StarlarkBool;
65use crate::values::function::BoundMethodGen;
66use crate::values::function::FrozenBoundMethod;
67use crate::values::list::ListRef;
68use crate::values::range::Range;
69use crate::values::string::interpolation::parse_percent_s_one;
70use crate::values::types::dict::Dict;
71use crate::values::types::ellipsis::Ellipsis;
72use crate::values::types::float::StarlarkFloat;
73use crate::values::types::int::inline_int::InlineInt;
74use crate::values::types::int::int_or_big::StarlarkInt;
75use crate::values::types::list::value::FrozenListData;
76use crate::values::types::list::value::ListData;
77use crate::values::types::string::dot_format::format_one;
78use crate::values::types::string::interpolation::percent_s_one;
79use crate::values::types::tuple::value::Tuple;
80use crate::values::types::unbound::UnboundValue;
81use crate::values::FrozenHeap;
82use crate::values::FrozenRef;
83use crate::values::FrozenStringValue;
84use crate::values::FrozenValue;
85use crate::values::FrozenValueTyped;
86use crate::values::Heap;
87use crate::values::StarlarkValue;
88use crate::values::Value;
89use crate::values::ValueError;
90use crate::values::ValueLike;
91
92/// `bool` operation.
93#[derive(Copy, Clone, Dupe, Eq, PartialEq, Debug)]
94pub(crate) enum MaybeNot {
95    Id,
96    Not,
97}
98
99impl MaybeNot {
100    pub(crate) fn negate(self) -> MaybeNot {
101        match self {
102            MaybeNot::Id => MaybeNot::Not,
103            MaybeNot::Not => MaybeNot::Id,
104        }
105    }
106}
107
108/// Map result of comparison to boolean.
109#[derive(Copy, Clone, Dupe, Debug)]
110pub(crate) enum CompareOp {
111    Less,
112    Greater,
113    LessOrEqual,
114    GreaterOrEqual,
115}
116
117impl CompareOp {
118    fn apply(self, x: Ordering) -> bool {
119        match self {
120            CompareOp::Less => x == Ordering::Less,
121            CompareOp::Greater => x == Ordering::Greater,
122            CompareOp::LessOrEqual => x != Ordering::Greater,
123            CompareOp::GreaterOrEqual => x != Ordering::Less,
124        }
125    }
126}
127
128/// Builtin function with one argument.
129#[derive(Clone, Debug, VisitSpanMut)]
130pub(crate) enum Builtin1 {
131    Minus,
132    /// `+x`.
133    Plus,
134    /// `~x`.
135    BitNot,
136    /// `not x`.
137    Not,
138    /// `type(arg) == "y"`
139    TypeIs(FrozenStringValue),
140    /// `"aaa%sbbb" % arg`
141    PercentSOne(FrozenStringValue, FrozenStringValue),
142    /// `"aaa%sbbb".format(arg)`
143    FormatOne(FrozenStringValue, FrozenStringValue),
144    /// `x.field`.
145    Dot(Symbol),
146}
147
148impl Builtin1 {
149    fn eval<'v>(&self, v: FrozenValue, ctx: &mut OptCtx<'v, '_, '_, '_>) -> Option<Value<'v>> {
150        match self {
151            Builtin1::Minus => v.to_value().minus(ctx.heap()).ok(),
152            Builtin1::Plus => v.to_value().plus(ctx.heap()).ok(),
153            Builtin1::BitNot => v.to_value().bit_not(ctx.heap()).ok(),
154            Builtin1::Not => Some(Value::new_bool(!v.to_value().to_bool())),
155            Builtin1::TypeIs(t) => Some(Value::new_bool(v.to_value().get_type_value() == *t)),
156            Builtin1::FormatOne(before, after) => {
157                Some(format_one(before, v.to_value(), after, ctx.heap()).to_value())
158            }
159            Builtin1::PercentSOne(before, after) => {
160                percent_s_one(before, v.to_value(), after, ctx.heap())
161                    .map(|s| s.to_value())
162                    .ok()
163            }
164            Builtin1::Dot(field) => {
165                Some(ExprCompiled::compile_time_getattr(v, field, ctx)?.to_value())
166            }
167        }
168    }
169}
170
171/// Builtin function with two arguments.
172#[derive(Copy, Clone, Dupe, Debug, VisitSpanMut)]
173pub(crate) enum Builtin2 {
174    /// `a == b`.
175    Equals,
176    /// `a in b`.
177    In,
178    /// `a - b`.
179    Sub,
180    /// `a + b`.
181    Add,
182    /// `a * b`.
183    Multiply,
184    /// `a % b`.
185    Percent,
186    /// `a / b`.
187    Divide,
188    /// `a // b`.
189    FloorDivide,
190    /// `a & b`.
191    BitAnd,
192    /// `a | b`.
193    BitOr,
194    /// `a ^ b`.
195    BitXor,
196    /// `a << b`.
197    LeftShift,
198    /// `a >> b`.
199    RightShift,
200    /// `a <=> b`.
201    Compare(CompareOp),
202    /// `a[b]`.
203    ArrayIndex,
204}
205
206impl Builtin2 {
207    fn eval<'v>(self, a: Value<'v>, b: Value<'v>, heap: &'v Heap) -> crate::Result<Value<'v>> {
208        match self {
209            Builtin2::Equals => a.equals(b).map(Value::new_bool),
210            Builtin2::Compare(cmp) => a.compare(b).map(|c| Value::new_bool(cmp.apply(c))),
211            Builtin2::In => b.is_in(a).map(Value::new_bool),
212            Builtin2::Sub => a.sub(b, heap),
213            Builtin2::Add => a.add(b, heap),
214            Builtin2::Multiply => a.mul(b, heap),
215            Builtin2::Percent => a.percent(b, heap),
216            Builtin2::Divide => a.div(b, heap),
217            Builtin2::FloorDivide => a.floor_div(b, heap),
218            Builtin2::BitAnd => a.bit_and(b, heap),
219            Builtin2::BitOr => a.bit_or(b, heap),
220            Builtin2::BitXor => a.bit_xor(b, heap),
221            Builtin2::LeftShift => a.left_shift(b, heap),
222            Builtin2::RightShift => a.right_shift(b, heap),
223            Builtin2::ArrayIndex => a.at(b, heap),
224        }
225    }
226}
227
228/// Logical binary operator.
229#[derive(Copy, Clone, Dupe, Debug, VisitSpanMut, Eq, PartialEq)]
230pub(crate) enum ExprLogicalBinOp {
231    And,
232    Or,
233}
234
235#[derive(Clone, Debug, VisitSpanMut)]
236pub(crate) enum ExprCompiled {
237    Value(FrozenValue),
238    /// Read local non-captured variable.
239    Local(LocalSlotId),
240    /// Read local captured variable.
241    LocalCaptured(LocalCapturedSlotId),
242    Module(ModuleSlotId),
243    Tuple(Vec<IrSpanned<ExprCompiled>>),
244    List(Vec<IrSpanned<ExprCompiled>>),
245    Dict(Vec<(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>)>),
246    /// Comprehension.
247    Compr(ComprCompiled),
248    If(
249        Box<(
250            // Condition.
251            IrSpanned<ExprCompiled>,
252            // Then branch.
253            IrSpanned<ExprCompiled>,
254            // Else branch.
255            IrSpanned<ExprCompiled>,
256        )>,
257    ),
258    Slice(
259        Box<(
260            IrSpanned<ExprCompiled>,
261            Option<IrSpanned<ExprCompiled>>,
262            Option<IrSpanned<ExprCompiled>>,
263            Option<IrSpanned<ExprCompiled>>,
264        )>,
265    ),
266    Builtin1(Builtin1, Box<IrSpanned<ExprCompiled>>),
267    LogicalBinOp(
268        ExprLogicalBinOp,
269        Box<(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>)>,
270    ),
271    /// Expression equivalent to `(x, y)[1]`: evaluate `x`, discard the result,
272    /// then evaluate `y` and use its result.
273    Seq(Box<(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>)>),
274    Builtin2(
275        Builtin2,
276        Box<(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>)>,
277    ),
278    Index2(
279        Box<(
280            IrSpanned<ExprCompiled>,
281            IrSpanned<ExprCompiled>,
282            IrSpanned<ExprCompiled>,
283        )>,
284    ),
285    Call(Box<IrSpanned<CallCompiled>>),
286    Def(DefCompiled),
287}
288
289impl ExprCompiled {
290    pub fn as_value(&self) -> Option<FrozenValue> {
291        match self {
292            Self::Value(x) => Some(*x),
293            _ => None,
294        }
295    }
296
297    /// Expression is known to be a constant which is a `def`.
298    pub(crate) fn as_frozen_def(&self) -> Option<FrozenValueTyped<FrozenDef>> {
299        FrozenValueTyped::new(self.as_value()?)
300    }
301
302    /// Expression is known to be a frozen bound method.
303    pub(crate) fn as_frozen_bound_method(&self) -> Option<FrozenValueTyped<FrozenBoundMethod>> {
304        FrozenValueTyped::new(self.as_value()?)
305    }
306
307    /// Expression is builtin `len` function.
308    pub(crate) fn is_fn_len(&self) -> bool {
309        match self.as_value() {
310            Some(value) => value == Constants::get().fn_len,
311            None => false,
312        }
313    }
314
315    /// Expression is builtin `type` function.
316    pub(crate) fn is_fn_type(&self) -> bool {
317        match self.as_value() {
318            Some(value) => value == Constants::get().fn_type,
319            None => false,
320        }
321    }
322
323    /// Expression is builtin `isinstance` function.
324    pub(crate) fn is_fn_isinstance(&self) -> bool {
325        match self.as_value() {
326            Some(value) => value == Constants::get().fn_isinstance,
327            None => false,
328        }
329    }
330
331    /// If expression is `type(x)`, return `x`.
332    pub(crate) fn as_type(&self) -> Option<&IrSpanned<ExprCompiled>> {
333        match self {
334            Self::Call(c) => c.as_type(),
335            _ => None,
336        }
337    }
338
339    /// If expression if `type(x) == t`, return `x` and `t`.
340    pub(crate) fn as_type_is(&self) -> Option<(&IrSpanned<ExprCompiled>, FrozenStringValue)> {
341        match self {
342            ExprCompiled::Builtin1(Builtin1::TypeIs(t), x) => Some((x, *t)),
343            _ => None,
344        }
345    }
346
347    /// Expression is a frozen value which is builtin.
348    pub(crate) fn as_builtin_value(&self) -> Option<FrozenValue> {
349        match self {
350            Self::Value(x) if x.is_builtin() => Some(*x),
351            _ => None,
352        }
353    }
354
355    /// Is expression a constant string?
356    pub(crate) fn as_string(&self) -> Option<FrozenStringValue> {
357        FrozenStringValue::new(self.as_value()?)
358    }
359
360    /// Iterable produced by this expression results in empty.
361    pub(crate) fn is_iterable_empty(&self) -> bool {
362        match self {
363            ExprCompiled::List(xs) => xs.is_empty(),
364            ExprCompiled::Tuple(xs) => xs.is_empty(),
365            ExprCompiled::Dict(xs) => xs.is_empty(),
366            ExprCompiled::Value(v) if v.is_builtin() => {
367                v.to_value().length().map_or(false, |l| l == 0)
368            }
369            _ => false,
370        }
371    }
372
373    /// Result of this expression is definitely `bool`
374    /// (if `false` it may also be `bool`).
375    fn is_definitely_bool(&self) -> bool {
376        match self {
377            Self::Value(v) => v.unpack_bool().is_some(),
378            Self::Builtin1(Builtin1::Not | Builtin1::TypeIs(_), _)
379            | Self::Builtin2(Builtin2::In | Builtin2::Equals | Builtin2::Compare(_), ..) => true,
380            _ => false,
381        }
382    }
383
384    /// This expression is definitely:
385    /// * infallible
386    /// * has no effects
387    pub(crate) fn is_pure_infallible(&self) -> bool {
388        match self {
389            Self::Value(..) => true,
390            Self::List(xs) | Self::Tuple(xs) => xs.iter().all(|x| x.is_pure_infallible()),
391            Self::Dict(xs) => xs.is_empty(),
392            Self::Builtin1(Builtin1::Not | Builtin1::TypeIs(_), x) => x.is_pure_infallible(),
393            Self::Seq(x_y) => {
394                let (x, y) = &**x_y;
395                x.is_pure_infallible() && y.is_pure_infallible()
396            }
397            Self::LogicalBinOp(_op, x_y) => {
398                let (x, y) = &**x_y;
399                x.is_pure_infallible() && y.is_pure_infallible()
400            }
401            Self::If(cond_x_y) => {
402                let (cond, x, y) = &**cond_x_y;
403                cond.is_pure_infallible() && x.is_pure_infallible() && y.is_pure_infallible()
404            }
405            Self::Call(call) => call.is_pure_infallible(),
406            _ => false,
407        }
408    }
409
410    /// If this expression is pure, infallible, and known to produce a value,
411    /// return truth of that value.
412    pub(crate) fn is_pure_infallible_to_bool(&self) -> Option<bool> {
413        match self {
414            ExprCompiled::Value(v) => Some(v.to_value().to_bool()),
415            ExprCompiled::List(xs) | ExprCompiled::Tuple(xs)
416                if xs.iter().all(|x| x.is_pure_infallible()) =>
417            {
418                Some(!xs.is_empty())
419            }
420            // TODO(nga): if keys are unique hashable constants, we can fold this to constant too.
421            ExprCompiled::Dict(xs) if xs.is_empty() => Some(false),
422            ExprCompiled::Builtin1(Builtin1::Not, x) => x.is_pure_infallible_to_bool().map(|x| !x),
423            ExprCompiled::LogicalBinOp(op, x_y) => {
424                let (x, y) = &**x_y;
425                match (
426                    op,
427                    x.is_pure_infallible_to_bool(),
428                    y.is_pure_infallible_to_bool(),
429                ) {
430                    (ExprLogicalBinOp::And, Some(true), y) => y,
431                    (ExprLogicalBinOp::Or, Some(false), y) => y,
432                    (ExprLogicalBinOp::And, Some(false), _) => Some(false),
433                    (ExprLogicalBinOp::Or, Some(true), _) => Some(true),
434                    (_, None, _) => None,
435                }
436            }
437            _ => None,
438        }
439    }
440
441    /// This expression is local slot.
442    pub(crate) fn as_local_non_captured(&self) -> Option<LocalSlotId> {
443        match self {
444            ExprCompiled::Local(slot) => Some(*slot),
445            _ => None,
446        }
447    }
448}
449
450enum ExprShortList<'a> {
451    Exprs(&'a [IrSpanned<ExprCompiled>]),
452    Constants(&'a [FrozenValue]),
453}
454
455impl<'a> IrSpanned<ExprShortList<'a>> {
456    fn as_exprs(&self) -> Vec<IrSpanned<ExprCompiled>> {
457        match &self.node {
458            ExprShortList::Exprs(exprs) => exprs.to_vec(),
459            ExprShortList::Constants(constants) => constants
460                .iter()
461                .map(|c| IrSpanned {
462                    node: ExprCompiled::Value(*c),
463                    span: self.span,
464                })
465                .collect(),
466        }
467    }
468}
469
470impl IrSpanned<ExprCompiled> {
471    /// Try to extract `[e0, e1, ..., en]` from this expression.
472    fn as_short_list(&self) -> Option<IrSpanned<ExprShortList>> {
473        // Prevent exponential explosion during optimization.
474        const MAX_LEN: usize = 1000;
475        match &self.node {
476            ExprCompiled::List(xs) if xs.len() <= MAX_LEN => Some(ExprShortList::Exprs(xs)),
477            ExprCompiled::Value(v) => {
478                let list = FrozenListData::from_frozen_value(v)?;
479                if list.len() <= MAX_LEN {
480                    Some(ExprShortList::Constants(list.content()))
481                } else {
482                    None
483                }
484            }
485            _ => None,
486        }
487        .map(|node| IrSpanned {
488            node,
489            span: self.span,
490        })
491    }
492
493    pub(crate) fn optimize(&self, ctx: &mut OptCtx) -> IrSpanned<ExprCompiled> {
494        let span = self.span;
495        let expr = match &self.node {
496            e @ (ExprCompiled::Value(..)
497            | ExprCompiled::Local(..)
498            | ExprCompiled::LocalCaptured(..)) => e.clone(),
499            ExprCompiled::Module(slot) => {
500                match ctx.frozen_module().and_then(|m| m.get_slot(*slot)) {
501                    None => {
502                        // Let if fail at runtime.
503                        ExprCompiled::Module(*slot)
504                    }
505                    Some(v) => ExprCompiled::Value(v),
506                }
507            }
508            ExprCompiled::Tuple(xs) => {
509                ExprCompiled::tuple(xs.map(|e| e.optimize(ctx)), ctx.frozen_heap())
510            }
511            ExprCompiled::List(xs) => ExprCompiled::List(xs.map(|e| e.optimize(ctx))),
512            ExprCompiled::Dict(kvs) => {
513                ExprCompiled::Dict(kvs.map(|(k, v)| (k.optimize(ctx), v.optimize(ctx))))
514            }
515            ExprCompiled::Compr(compr) => compr.optimize(ctx),
516            ExprCompiled::If(cond_t_f) => {
517                let (cond, t, f) = &**cond_t_f;
518                let cond = cond.optimize(ctx);
519                let t = t.optimize(ctx);
520                let f = f.optimize(ctx);
521                return ExprCompiled::if_expr(cond, t, f);
522            }
523            ExprCompiled::Slice(v_start_stop_step) => {
524                let (v, start, stop, step) = &**v_start_stop_step;
525                let v = v.optimize(ctx);
526                let start = start.as_ref().map(|x| x.optimize(ctx));
527                let stop = stop.as_ref().map(|x| x.optimize(ctx));
528                let step = step.as_ref().map(|x| x.optimize(ctx));
529                ExprCompiled::slice(span, v, start, stop, step, ctx)
530            }
531            ExprCompiled::Builtin1(op, e) => {
532                let e = e.optimize(ctx);
533                ExprCompiled::un_op(span, op, e, ctx)
534            }
535            ExprCompiled::LogicalBinOp(op, l_r) => {
536                let (l, r) = &**l_r;
537                let l = l.optimize(ctx);
538                let r = r.optimize(ctx);
539                return ExprCompiled::logical_bin_op(*op, l, r);
540            }
541            ExprCompiled::Seq(l_r) => {
542                let (l, r) = &**l_r;
543                let l = l.optimize(ctx);
544                let r = r.optimize(ctx);
545                return ExprCompiled::seq(l, r);
546            }
547            ExprCompiled::Builtin2(op, l_r) => {
548                let (l, r) = &**l_r;
549                let l = l.optimize(ctx);
550                let r = r.optimize(ctx);
551                ExprCompiled::bin_op(*op, l, r, ctx)
552            }
553            ExprCompiled::Index2(a_i0_i1) => {
554                let (a, i0, i1) = &**a_i0_i1;
555                let a = a.optimize(ctx);
556                let i0 = i0.optimize(ctx);
557                let i1 = i1.optimize(ctx);
558                ExprCompiled::index2(a, i0, i1)
559            }
560            d @ ExprCompiled::Def(..) => (*d).clone(),
561            ExprCompiled::Call(ref call) => call.optimize(ctx),
562        };
563        IrSpanned { node: expr, span }
564    }
565}
566
567impl ExprCompiled {
568    fn equals(l: IrSpanned<ExprCompiled>, r: IrSpanned<ExprCompiled>) -> IrSpanned<ExprCompiled> {
569        let span = l.span.merge(&r.span);
570        if let (Some(l), Some(r)) = (l.as_value(), r.as_value()) {
571            // If comparison fails, let it fail in runtime.
572            if let Ok(r) = l.equals(r.to_value()) {
573                return IrSpanned {
574                    span,
575                    node: ExprCompiled::Value(FrozenValue::new_bool(r)),
576                };
577            }
578        }
579
580        let (l, r) = match try_eval_type_is(l, r) {
581            Ok(e) => return e,
582            Err((l, r)) => (l, r),
583        };
584
585        let (r, l) = match try_eval_type_is(r, l) {
586            Ok(e) => return e,
587            Err((r, l)) => (r, l),
588        };
589
590        IrSpanned {
591            span,
592            node: ExprCompiled::Builtin2(Builtin2::Equals, Box::new((l, r))),
593        }
594    }
595
596    pub(crate) fn not(span: FrameSpan, expr: IrSpanned<ExprCompiled>) -> IrSpanned<ExprCompiled> {
597        match expr.node {
598            ExprCompiled::Value(x) => IrSpanned {
599                node: ExprCompiled::Value(FrozenValue::new_bool(!x.to_value().to_bool())),
600                span,
601            },
602            // Collapse `not not e` to `e` only if `e` is known to produce a boolean.
603            ExprCompiled::Builtin1(Builtin1::Not, e) if e.is_definitely_bool() => (*e).clone(),
604            _ => IrSpanned {
605                node: ExprCompiled::Builtin1(Builtin1::Not, Box::new(expr)),
606                span,
607            },
608        }
609    }
610
611    fn or(l: IrSpanned<ExprCompiled>, r: IrSpanned<ExprCompiled>) -> IrSpanned<ExprCompiled> {
612        Self::logical_bin_op(ExprLogicalBinOp::Or, l, r)
613    }
614
615    fn and(l: IrSpanned<ExprCompiled>, r: IrSpanned<ExprCompiled>) -> IrSpanned<ExprCompiled> {
616        Self::logical_bin_op(ExprLogicalBinOp::And, l, r)
617    }
618
619    pub(crate) fn logical_bin_op(
620        op: ExprLogicalBinOp,
621        l: IrSpanned<ExprCompiled>,
622        r: IrSpanned<ExprCompiled>,
623    ) -> IrSpanned<ExprCompiled> {
624        if let Some(l_v) = l.is_pure_infallible_to_bool() {
625            if l_v == (op == ExprLogicalBinOp::Or) {
626                l
627            } else {
628                r
629            }
630        } else {
631            let span = l.span.merge(&r.span);
632            IrSpanned {
633                node: ExprCompiled::LogicalBinOp(op, Box::new((l, r))),
634                span,
635            }
636        }
637    }
638
639    pub(crate) fn seq(
640        l: IrSpanned<ExprCompiled>,
641        r: IrSpanned<ExprCompiled>,
642    ) -> IrSpanned<ExprCompiled> {
643        if l.is_pure_infallible() {
644            r
645        } else {
646            let span = l.span.merge(&r.span);
647            IrSpanned {
648                node: ExprCompiled::Seq(Box::new((l, r))),
649                span,
650            }
651        }
652    }
653
654    fn percent(
655        l: IrSpanned<ExprCompiled>,
656        r: IrSpanned<ExprCompiled>,
657        ctx: &mut OptCtx,
658    ) -> ExprCompiled {
659        if let Some(v) = l.as_string() {
660            if let Some((before, after)) = parse_percent_s_one(&v) {
661                let before = ctx.frozen_heap().alloc_str_intern(&before);
662                let after = ctx.frozen_heap().alloc_str_intern(&after);
663                return ExprCompiled::percent_s_one(before, r, after, ctx);
664            }
665        }
666        ExprCompiled::Builtin2(Builtin2::Percent, Box::new((l, r)))
667    }
668
669    fn percent_s_one(
670        before: FrozenStringValue,
671        arg: IrSpanned<ExprCompiled>,
672        after: FrozenStringValue,
673        ctx: &mut OptCtx,
674    ) -> ExprCompiled {
675        if let Some(arg) = arg.as_value() {
676            if let Ok(value) =
677                percent_s_one(before.as_str(), arg.to_value(), after.as_str(), ctx.heap())
678            {
679                let value = ctx.frozen_heap().alloc_str_intern(value.as_str());
680                return ExprCompiled::Value(value.to_frozen_value());
681            }
682        }
683
684        ExprCompiled::Builtin1(Builtin1::PercentSOne(before, after), Box::new(arg))
685    }
686
687    pub(crate) fn format_one(
688        before: FrozenStringValue,
689        arg: IrSpanned<ExprCompiled>,
690        after: FrozenStringValue,
691        ctx: &mut OptCtx,
692    ) -> ExprCompiled {
693        if let Some(arg) = arg.as_value() {
694            let value = format_one(&before, arg.to_value(), &after, ctx.heap());
695            let value = ctx.frozen_heap().alloc_str_intern(value.as_str());
696            return ExprCompiled::Value(value.to_frozen_value());
697        }
698
699        ExprCompiled::Builtin1(Builtin1::FormatOne(before, after), Box::new(arg))
700    }
701
702    fn add(l: IrSpanned<ExprCompiled>, r: IrSpanned<ExprCompiled>) -> ExprCompiled {
703        if let (Some(l), Some(r)) = (l.as_short_list(), r.as_short_list()) {
704            return ExprCompiled::List(l.as_exprs().into_iter().chain(r.as_exprs()).collect());
705        }
706        ExprCompiled::Builtin2(Builtin2::Add, Box::new((l, r)))
707    }
708
709    pub(crate) fn bin_op(
710        bin_op: Builtin2,
711        l: IrSpanned<ExprCompiled>,
712        r: IrSpanned<ExprCompiled>,
713        ctx: &mut OptCtx,
714    ) -> ExprCompiled {
715        let span = l.span.merge(&r.span);
716        // Binary operators should have no side effects,
717        // but to avoid possible problems, we only fold binary operators on builtin types.
718        if let (Some(l), Some(r)) = (l.as_builtin_value(), r.as_builtin_value()) {
719            if let Ok(v) = bin_op.eval(l.to_value(), r.to_value(), ctx.heap()) {
720                if let Some(v) = ExprCompiled::try_value(span, v, ctx.frozen_heap()) {
721                    return v;
722                }
723            }
724        }
725
726        match bin_op {
727            Builtin2::Percent => ExprCompiled::percent(l, r, ctx),
728            Builtin2::Add => ExprCompiled::add(l, r),
729            Builtin2::Equals => ExprCompiled::equals(l, r).node,
730            Builtin2::ArrayIndex => ExprCompiled::index(l, r, ctx),
731            bin_op => ExprCompiled::Builtin2(bin_op, Box::new((l, r))),
732        }
733    }
734
735    pub(crate) fn if_expr(
736        cond: IrSpanned<ExprCompiled>,
737        t: IrSpanned<ExprCompiled>,
738        f: IrSpanned<ExprCompiled>,
739    ) -> IrSpanned<ExprCompiled> {
740        let cond_span = cond.span;
741        let cond = ExprCompiledBool::new(cond);
742        match cond.node {
743            ExprCompiledBool::Const(true) => t,
744            ExprCompiledBool::Const(false) => f,
745            ExprCompiledBool::Expr(cond) => match cond {
746                ExprCompiled::Builtin1(Builtin1::Not, cond) => ExprCompiled::if_expr(*cond, f, t),
747                ExprCompiled::Seq(x_cond) => {
748                    let (x, cond) = *x_cond;
749                    ExprCompiled::seq(x, ExprCompiled::if_expr(cond, t, f))
750                }
751                cond => {
752                    let cond = IrSpanned {
753                        node: cond,
754                        span: cond_span,
755                    };
756                    let span = cond.span.merge(&t.span).merge(&f.span);
757                    IrSpanned {
758                        node: ExprCompiled::If(Box::new((cond, t, f))),
759                        span,
760                    }
761                }
762            },
763        }
764    }
765
766    pub(crate) fn un_op(
767        span: FrameSpan,
768        op: &Builtin1,
769        expr: IrSpanned<ExprCompiled>,
770        ctx: &mut OptCtx,
771    ) -> ExprCompiled {
772        if let Some(v) = expr.as_builtin_value() {
773            if let Some(v) = op.eval(v, ctx) {
774                if let Some(v) = ExprCompiled::try_value(expr.span, v, ctx.frozen_heap()) {
775                    return v;
776                }
777            }
778        }
779        match op {
780            Builtin1::FormatOne(before, after) => {
781                ExprCompiled::format_one(*before, expr, *after, ctx)
782            }
783            Builtin1::PercentSOne(before, after) => {
784                ExprCompiled::percent_s_one(*before, expr, *after, ctx)
785            }
786            Builtin1::Dot(field) => ExprCompiled::dot(expr, field, ctx),
787            Builtin1::TypeIs(t) => ExprCompiled::type_is(expr, *t),
788            Builtin1::Not => ExprCompiled::not(span, expr).node,
789            op => ExprCompiled::Builtin1(op.clone(), Box::new(expr)),
790        }
791    }
792
793    fn try_values(
794        span: FrameSpan,
795        values: &[Value],
796        heap: &FrozenHeap,
797    ) -> Option<Vec<IrSpanned<ExprCompiled>>> {
798        values
799            .try_map(|v| {
800                Self::try_value(span, *v, heap)
801                    .map(|expr| IrSpanned { span, node: expr })
802                    .ok_or(())
803            })
804            .ok()
805    }
806
807    /// Try convert a maybe not frozen value to an expression, or discard it.
808    pub(crate) fn try_value(span: FrameSpan, v: Value, heap: &FrozenHeap) -> Option<ExprCompiled> {
809        if let Some(v) = v.unpack_frozen() {
810            // If frozen, we are lucky.
811            Some(ExprCompiled::Value(v))
812        } else if let Some(v) = v.unpack_str() {
813            if v.len() <= 1000 {
814                // If string, copy it to frozen heap.
815                Some(ExprCompiled::Value(
816                    heap.alloc_str_intern(v).to_frozen_value(),
817                ))
818            } else {
819                // Long strings may lead to exponential explosion in the optimizer,
820                // so skips optimizations for them.
821                None
822            }
823        } else if let Some(v) = v.downcast_ref::<StarlarkFloat>() {
824            Some(ExprCompiled::Value(heap.alloc(*v)))
825        } else if let Some(v) = v.downcast_ref::<Range>() {
826            Some(ExprCompiled::Value(heap.alloc(*v)))
827        } else if let Some(v) = ListRef::from_value(v) {
828            // When spec-safe function returned a non-frozen list,
829            // we try to convert that list to a list of constants instruction.
830            let items = Self::try_values(span, v.content(), heap)?;
831            Some(ExprCompiled::List(items))
832        } else if let Some(v) = Tuple::from_value(v) {
833            let items = Self::try_values(span, v.content(), heap)?;
834            Some(Self::tuple(items, heap))
835        } else {
836            None
837        }
838    }
839
840    pub(crate) fn compr(compr: ComprCompiled) -> ExprCompiled {
841        match compr {
842            ComprCompiled::List(x, clauses) => {
843                if clauses.is_nop() {
844                    ExprCompiled::List(Vec::new())
845                } else {
846                    ExprCompiled::Compr(ComprCompiled::List(x, clauses))
847                }
848            }
849            ComprCompiled::Dict(k_v, clauses) => {
850                let (k, v) = *k_v;
851                if clauses.is_nop() {
852                    ExprCompiled::Dict(Vec::new())
853                } else {
854                    ExprCompiled::Compr(ComprCompiled::Dict(Box::new((k, v)), clauses))
855                }
856            }
857        }
858    }
859
860    /// Construct tuple expression from elements optimizing to frozen tuple value when possible.
861    pub(crate) fn tuple(elems: Vec<IrSpanned<ExprCompiled>>, heap: &FrozenHeap) -> ExprCompiled {
862        if let Ok(elems) = elems.try_map(|e| e.as_value().ok_or(())) {
863            ExprCompiled::Value(heap.alloc_tuple(&elems))
864        } else {
865            ExprCompiled::Tuple(elems)
866        }
867    }
868
869    pub(crate) fn compile_time_getattr(
870        left: FrozenValue,
871        attr: &Symbol,
872        ctx: &mut OptCtx,
873    ) -> Option<FrozenValue> {
874        // We assume `getattr` has no side effects.
875        let v = get_attr_hashed_raw(left.to_value(), attr, ctx.heap()).ok()?;
876        match v {
877            MemberOrValue::Member(m) => match m {
878                UnboundValue::Method(m, _) => Some(
879                    ctx.frozen_heap()
880                        .alloc_simple(BoundMethodGen::new(left, *m)),
881                ),
882                UnboundValue::Attr(..) => None,
883            },
884            MemberOrValue::Value(v) => v.unpack_frozen(),
885        }
886    }
887
888    pub(crate) fn dot(
889        object: IrSpanned<ExprCompiled>,
890        field: &Symbol,
891        ctx: &mut OptCtx,
892    ) -> ExprCompiled {
893        if let Some(left) = object.as_value() {
894            if let Some(v) = Self::compile_time_getattr(left, field, ctx) {
895                return ExprCompiled::Value(v);
896            }
897        }
898
899        ExprCompiled::Builtin1(Builtin1::Dot(field.clone()), Box::new(object))
900    }
901
902    fn slice(
903        span: FrameSpan,
904        array: IrSpanned<ExprCompiled>,
905        start: Option<IrSpanned<ExprCompiled>>,
906        stop: Option<IrSpanned<ExprCompiled>>,
907        step: Option<IrSpanned<ExprCompiled>>,
908        ctx: &mut OptCtx,
909    ) -> ExprCompiled {
910        if let (Some(array), Some(start), Some(stop), Some(step)) = (
911            array.as_builtin_value(),
912            start.as_ref().map(|e| e.as_value()),
913            stop.as_ref().map(|e| e.as_value()),
914            step.as_ref().map(|e| e.as_value()),
915        ) {
916            if let Ok(v) = array.to_value().slice(
917                start.map(|v| v.to_value()),
918                stop.map(|v| v.to_value()),
919                step.map(|v| v.to_value()),
920                ctx.heap(),
921            ) {
922                if let Some(v) = ExprCompiled::try_value(span, v, ctx.frozen_heap()) {
923                    return v;
924                }
925            }
926        }
927        ExprCompiled::Slice(Box::new((array, start, stop, step)))
928    }
929
930    pub(crate) fn index(
931        array: IrSpanned<ExprCompiled>,
932        index: IrSpanned<ExprCompiled>,
933        ctx: &mut OptCtx,
934    ) -> ExprCompiled {
935        let span = array.span.merge(&index.span);
936        if let (Some(array), Some(index)) = (array.as_builtin_value(), index.as_value()) {
937            if let Ok(v) = array.to_value().at(index.to_value(), ctx.heap()) {
938                if let Some(expr) = ExprCompiled::try_value(span, v, ctx.frozen_heap()) {
939                    return expr;
940                }
941            }
942        }
943        ExprCompiled::Builtin2(Builtin2::ArrayIndex, Box::new((array, index)))
944    }
945
946    pub(crate) fn index2(
947        array: IrSpanned<ExprCompiled>,
948        index0: IrSpanned<ExprCompiled>,
949        index1: IrSpanned<ExprCompiled>,
950    ) -> ExprCompiled {
951        ExprCompiled::Index2(Box::new((array, index0, index1)))
952    }
953
954    pub(crate) fn typ(span: FrameSpan, v: IrSpanned<ExprCompiled>) -> ExprCompiled {
955        match &v.node {
956            ExprCompiled::Value(v) => {
957                ExprCompiled::Value(v.to_value().get_type_value().to_frozen_value())
958            }
959            ExprCompiled::Tuple(xs) if xs.iter().all(|e| e.is_pure_infallible()) => {
960                ExprCompiled::Value(Tuple::get_type_value_static().to_frozen_value())
961            }
962            ExprCompiled::List(xs) if xs.iter().all(|e| e.is_pure_infallible()) => {
963                ExprCompiled::Value(ListData::get_type_value_static().to_frozen_value())
964            }
965            ExprCompiled::Dict(xs) if xs.is_empty() => {
966                ExprCompiled::Value(Dict::get_type_value_static().to_frozen_value())
967            }
968            ExprCompiled::Builtin1(Builtin1::Not | Builtin1::TypeIs(_), x)
969                if x.is_pure_infallible() =>
970            {
971                ExprCompiled::Value(StarlarkBool::get_type_value_static().to_frozen_value())
972            }
973            _ => ExprCompiled::Call(Box::new(IrSpanned {
974                span,
975                node: CallCompiled {
976                    fun: IrSpanned {
977                        span,
978                        node: ExprCompiled::Value(Constants::get().fn_type.0),
979                    },
980                    args: ArgsCompiledValue {
981                        pos_named: vec![v],
982                        ..ArgsCompiledValue::default()
983                    },
984                },
985            })),
986        }
987    }
988
989    pub(crate) fn type_is(v: IrSpanned<ExprCompiled>, t: FrozenStringValue) -> ExprCompiled {
990        if let Some(v) = v.as_value() {
991            return ExprCompiled::Value(FrozenValue::new_bool(
992                v.to_value().get_type() == t.as_str(),
993            ));
994        }
995        ExprCompiled::Builtin1(Builtin1::TypeIs(t), Box::new(v))
996    }
997
998    pub(crate) fn len(span: FrameSpan, arg: IrSpanned<ExprCompiled>) -> ExprCompiled {
999        if let Some(arg) = arg.as_value() {
1000            if let Ok(len) = arg.to_value().length() {
1001                if let Ok(len) = InlineInt::try_from(len) {
1002                    return ExprCompiled::Value(FrozenValue::new_int(len));
1003                }
1004            }
1005        }
1006        ExprCompiled::Call(Box::new(IrSpanned {
1007            span,
1008            node: CallCompiled {
1009                fun: IrSpanned {
1010                    span,
1011                    node: ExprCompiled::Value(Constants::get().fn_len.0),
1012                },
1013                args: ArgsCompiledValue {
1014                    pos_named: vec![arg],
1015                    ..ArgsCompiledValue::default()
1016                },
1017            },
1018        }))
1019    }
1020}
1021
1022#[derive(Debug, Clone, Error)]
1023pub(crate) enum EvalError {
1024    #[error("Dictionary key repeated for `{0}`")]
1025    DuplicateDictionaryKey(String),
1026}
1027
1028/// Try fold expression `cmp(l == r)` into `cmp(type(x) == "y")`.
1029/// Return original `l` and `r` arguments if fold was unsuccessful.
1030fn try_eval_type_is(
1031    l: IrSpanned<ExprCompiled>,
1032    r: IrSpanned<ExprCompiled>,
1033) -> Result<IrSpanned<ExprCompiled>, (IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>)> {
1034    let span = l.span.merge(&r.span);
1035    if let (Some(l), Some(r)) = (l.as_type(), r.as_string()) {
1036        Ok(IrSpanned {
1037            span,
1038            node: ExprCompiled::type_is(l.clone(), r),
1039        })
1040    } else {
1041        Err((l, r))
1042    }
1043}
1044
1045trait AstLiteralCompile {
1046    fn compile(&self, heap: &FrozenHeap) -> FrozenValue;
1047}
1048
1049impl AstLiteralCompile for AstLiteral {
1050    fn compile(&self, heap: &FrozenHeap) -> FrozenValue {
1051        match self {
1052            AstLiteral::Int(i) => heap.alloc(StarlarkInt::from(i.node.clone())),
1053            AstLiteral::Float(f) => heap.alloc(f.node),
1054            AstLiteral::String(x) => heap.alloc(x.node.as_str()),
1055            AstLiteral::Ellipsis => heap.alloc(Ellipsis),
1056        }
1057    }
1058}
1059
1060trait CompilerExprUtil<P: AstPayload> {
1061    fn unpack_string_literal(&self) -> Option<&str>;
1062    fn reduces_to_string<'a>(
1063        op: BinOp,
1064        left: &'a AstExprP<P>,
1065        right: &'a AstExprP<P>,
1066    ) -> Option<String>;
1067}
1068
1069impl<P: AstPayload> CompilerExprUtil<P> for ExprP<P> {
1070    fn unpack_string_literal(&self) -> Option<&str> {
1071        match self {
1072            ExprP::Literal(AstLiteral::String(i)) => Some(&i.node),
1073            _ => None,
1074        }
1075    }
1076
1077    // Does an entire sequence of additions reduce to a string literal
1078    fn reduces_to_string<'a>(
1079        mut op: BinOp,
1080        mut left: &'a AstExprP<P>,
1081        mut right: &'a AstExprP<P>,
1082    ) -> Option<String> {
1083        let mut results = Vec::new();
1084        loop {
1085            if op != BinOp::Add {
1086                return None;
1087            }
1088            // a + b + c  associates as  (a + b) + c
1089            let x = right.unpack_string_literal()?;
1090            results.push(x.to_owned());
1091            match &left.node {
1092                ExprP::Op(left2, op2, right2) => {
1093                    op = *op2;
1094                    left = left2;
1095                    right = right2;
1096                }
1097                _ => {
1098                    let x = left.unpack_string_literal()?;
1099                    results.push(x.to_owned());
1100                    break;
1101                }
1102            }
1103        }
1104        results.reverse();
1105        Some(results.concat())
1106    }
1107}
1108
1109#[cold]
1110#[inline(never)]
1111fn get_attr_no_attr_error<'v>(x: Value<'v>, attribute: &Symbol) -> crate::Error {
1112    match did_you_mean(attribute.as_str(), x.dir_attr().iter().map(|s| s.as_str())) {
1113        None => ValueError::NoAttr(x.get_type().to_owned(), attribute.as_str().to_owned()).into(),
1114        Some(better) => ValueError::NoAttrDidYouMean(
1115            x.get_type().to_owned(),
1116            attribute.as_str().to_owned(),
1117            better.to_owned(),
1118        )
1119        .into(),
1120    }
1121}
1122
1123pub(crate) enum MemberOrValue<'v, 'a> {
1124    Member(&'a UnboundValue),
1125    Value(Value<'v>),
1126}
1127
1128impl<'v, 'a> MemberOrValue<'v, 'a> {
1129    #[inline]
1130    pub(crate) fn invoke(
1131        &self,
1132        this: Value<'v>,
1133        span: FrozenRef<'static, FrameSpan>,
1134        args: &Arguments<'v, '_>,
1135        eval: &mut Evaluator<'v, '_, '_>,
1136    ) -> crate::Result<Value<'v>> {
1137        match self {
1138            MemberOrValue::Member(member) => member.invoke_method(this, span, args, eval),
1139            MemberOrValue::Value(value) => value.invoke_with_loc(Some(span), args, eval),
1140        }
1141    }
1142}
1143
1144#[inline(always)]
1145pub(crate) fn get_attr_hashed_raw<'v>(
1146    x: Value<'v>,
1147    attribute: &Symbol,
1148    heap: &'v Heap,
1149) -> crate::Result<MemberOrValue<'v, 'static>> {
1150    let aref = x.get_ref();
1151    if let Some(methods) = aref.vtable().methods() {
1152        if let Some(v) = methods.get_frozen_symbol(attribute) {
1153            return Ok(MemberOrValue::Member(v));
1154        }
1155    }
1156    match aref.get_attr_hashed(attribute.as_str_hashed(), heap) {
1157        None => Err(get_attr_no_attr_error(x, attribute)),
1158        Some(x) => Ok(MemberOrValue::Value(x)),
1159    }
1160}
1161
1162pub(crate) fn get_attr_hashed_bind<'v>(
1163    x: Value<'v>,
1164    attribute: &Symbol,
1165    heap: &'v Heap,
1166) -> crate::Result<Value<'v>> {
1167    let aref = x.get_ref();
1168    if let Some(methods) = aref.vtable().methods() {
1169        if let Some(v) = methods.get_frozen_symbol(attribute) {
1170            return v.bind(x, heap);
1171        }
1172    }
1173    match aref.get_attr_hashed(attribute.as_str_hashed(), heap) {
1174        None => Err(get_attr_no_attr_error(x, attribute)),
1175        Some(x) => {
1176            // Only `get_methods` is allowed to return unbound methods or attributes.
1177            // Both types are crate private, so we assume `get_attr` never returns them.
1178            Ok(x)
1179        }
1180    }
1181}
1182
1183impl<'v, 'a, 'e> Compiler<'v, 'a, 'e, '_> {
1184    fn expr_ident(&mut self, ident: &CstIdent) -> ExprCompiled {
1185        let resolved_ident = ident
1186            .node
1187            .payload
1188            .as_ref()
1189            .unwrap_or_else(|| panic!("variable not resolved: `{}`", ident.node.ident));
1190        match resolved_ident {
1191            ResolvedIdent::Slot(Slot::Local(slot), binding_id) => {
1192                let binding = self.scope_data.get_binding(*binding_id);
1193
1194                // We can't look up the local variabless in advance, because they are different each time
1195                // we go through a new function call.
1196                match binding.captured {
1197                    Captured::Yes => ExprCompiled::LocalCaptured(LocalCapturedSlotId(slot.0)),
1198                    Captured::No => ExprCompiled::Local(LocalSlotId(slot.0)),
1199                }
1200            }
1201            ResolvedIdent::Slot(Slot::Module(slot), binding_id) => {
1202                let binding = self.scope_data.get_binding(*binding_id);
1203
1204                // We can only inline variables if they were assigned once
1205                // otherwise we might inline the wrong value.
1206                if binding.assign_count == AssignCount::AtMostOnce {
1207                    if let Some(v) = self.eval.module_env.slots().get_slot(*slot) {
1208                        // We could inline non-frozen values, but these values
1209                        // can be garbage-collected, so it is somewhat harder to implement.
1210                        if let Some(v) = v.unpack_frozen() {
1211                            return ExprCompiled::Value(v);
1212                        }
1213                    }
1214                }
1215
1216                ExprCompiled::Module(*slot)
1217            }
1218            ResolvedIdent::Global(v) => ExprCompiled::Value(*v),
1219        }
1220    }
1221
1222    fn opt_ctx<'s>(&'s mut self) -> OptCtx<'v, 'a, 'e, 's> {
1223        let param_count = self.current_scope().param_count();
1224        OptCtx::new(self.eval, param_count)
1225    }
1226
1227    pub(crate) fn expr(
1228        &mut self,
1229        expr: &CstExpr,
1230    ) -> Result<IrSpanned<ExprCompiled>, CompilerInternalError> {
1231        // println!("compile {}", expr.node);
1232        let span = FrameSpan::new(FrozenFileSpan::new(self.codemap, expr.span));
1233        let expr = match &expr.node {
1234            ExprP::Identifier(ident) => self.expr_ident(ident),
1235            ExprP::Lambda(l) => {
1236                let signature_span = l.signature_span();
1237                let signature_span = FrozenFileSpan::new(self.codemap, signature_span);
1238                let LambdaP {
1239                    params,
1240                    body,
1241                    payload: scope_id,
1242                } = l;
1243                let suite = Spanned {
1244                    span: expr.span,
1245                    // TODO(nga): unnecessary clone.
1246                    node: StmtP::Return(Some(*body.clone())),
1247                };
1248                self.function("lambda", signature_span, *scope_id, params, None, &suite)?
1249            }
1250            ExprP::Tuple(exprs) => {
1251                let xs = self.exprs(exprs)?;
1252                ExprCompiled::tuple(xs, self.eval.module_env.frozen_heap())
1253            }
1254            ExprP::List(exprs) => {
1255                let xs = self.exprs(exprs)?;
1256                ExprCompiled::List(xs)
1257            }
1258            ExprP::Dict(exprs) => {
1259                let xs = exprs
1260                    .iter()
1261                    .map(|(k, v)| Ok((self.expr(k)?, self.expr(v)?)))
1262                    .collect::<Result<_, CompilerInternalError>>()?;
1263                ExprCompiled::Dict(xs)
1264            }
1265            ExprP::If(cond_then_expr_else_expr) => {
1266                let (cond, then_expr, else_expr) = &**cond_then_expr_else_expr;
1267                let cond = self.expr(cond)?;
1268                let then_expr = self.expr(then_expr)?;
1269                let else_expr = self.expr(else_expr)?;
1270                return Ok(ExprCompiled::if_expr(cond, then_expr, else_expr));
1271            }
1272            ExprP::Dot(left, right) => {
1273                let left = self.expr(left)?;
1274                let s = Symbol::new(&right.node);
1275
1276                ExprCompiled::dot(left, &s, &mut self.opt_ctx())
1277            }
1278            ExprP::Call(left, args) => {
1279                let left = self.expr(left)?;
1280                let args = self.args(args)?;
1281                CallCompiled::call(span, left, args, &mut self.opt_ctx())
1282            }
1283            ExprP::Index(array_index) => {
1284                let (array, index) = &**array_index;
1285                let array = self.expr(array)?;
1286                let index = self.expr(index)?;
1287                ExprCompiled::index(array, index, &mut self.opt_ctx())
1288            }
1289            ExprP::Index2(array_index0_index1) => {
1290                let (array, index0, index1) = &**array_index0_index1;
1291                let array = self.expr(array)?;
1292                let index0 = self.expr(index0)?;
1293                let index1 = self.expr(index1)?;
1294                ExprCompiled::index2(array, index0, index1)
1295            }
1296            ExprP::Slice(collection, start, stop, stride) => {
1297                let collection = self.expr(collection)?;
1298                let start = start.as_ref().map(|x| self.expr(x)).transpose()?;
1299                let stop = stop.as_ref().map(|x| self.expr(x)).transpose()?;
1300                let stride = stride.as_ref().map(|x| self.expr(x)).transpose()?;
1301                ExprCompiled::slice(span, collection, start, stop, stride, &mut self.opt_ctx())
1302            }
1303            ExprP::Not(expr) => {
1304                let expr = self.expr(expr)?;
1305                return Ok(ExprCompiled::not(span, expr));
1306            }
1307            ExprP::Minus(expr) => {
1308                let expr = self.expr(expr)?;
1309                ExprCompiled::un_op(span, &Builtin1::Minus, expr, &mut self.opt_ctx())
1310            }
1311            ExprP::Plus(expr) => {
1312                let expr = self.expr(expr)?;
1313                ExprCompiled::un_op(span, &Builtin1::Plus, expr, &mut self.opt_ctx())
1314            }
1315            ExprP::BitNot(expr) => {
1316                let expr = self.expr(expr)?;
1317                ExprCompiled::un_op(span, &Builtin1::BitNot, expr, &mut self.opt_ctx())
1318            }
1319            ExprP::Op(left, op, right) => {
1320                if let Some(x) = ExprP::reduces_to_string(*op, left, right) {
1321                    // Note there's const propagation for `+` on compiled expressions,
1322                    // but special handling of `+` on AST might be slightly more efficient
1323                    // (no unnecessary allocations on the heap). So keep it.
1324                    let val = self.eval.module_env.frozen_heap().alloc(x);
1325                    ExprCompiled::Value(val)
1326                } else {
1327                    let right = if *op == BinOp::In || *op == BinOp::NotIn {
1328                        list_to_tuple(right)
1329                    } else {
1330                        Cow::Borrowed(&**right)
1331                    };
1332
1333                    let l = self.expr(left)?;
1334                    let r = self.expr(&right)?;
1335                    match op {
1336                        BinOp::Or => return Ok(ExprCompiled::or(l, r)),
1337                        BinOp::And => return Ok(ExprCompiled::and(l, r)),
1338                        BinOp::Equal => return Ok(ExprCompiled::equals(l, r)),
1339                        BinOp::NotEqual => {
1340                            return Ok(ExprCompiled::not(span, ExprCompiled::equals(l, r)));
1341                        }
1342                        BinOp::Less => ExprCompiled::bin_op(
1343                            Builtin2::Compare(CompareOp::Less),
1344                            l,
1345                            r,
1346                            &mut self.opt_ctx(),
1347                        ),
1348                        BinOp::Greater => ExprCompiled::bin_op(
1349                            Builtin2::Compare(CompareOp::Greater),
1350                            l,
1351                            r,
1352                            &mut self.opt_ctx(),
1353                        ),
1354                        BinOp::LessOrEqual => ExprCompiled::bin_op(
1355                            Builtin2::Compare(CompareOp::LessOrEqual),
1356                            l,
1357                            r,
1358                            &mut self.opt_ctx(),
1359                        ),
1360                        BinOp::GreaterOrEqual => ExprCompiled::bin_op(
1361                            Builtin2::Compare(CompareOp::GreaterOrEqual),
1362                            l,
1363                            r,
1364                            &mut self.opt_ctx(),
1365                        ),
1366                        BinOp::In => ExprCompiled::bin_op(Builtin2::In, l, r, &mut self.opt_ctx()),
1367                        BinOp::NotIn => {
1368                            ExprCompiled::not(
1369                                span,
1370                                IrSpanned {
1371                                    span,
1372                                    node: ExprCompiled::bin_op(
1373                                        Builtin2::In,
1374                                        l,
1375                                        r,
1376                                        &mut self.opt_ctx(),
1377                                    ),
1378                                },
1379                            )
1380                            .node
1381                        }
1382                        BinOp::Subtract => {
1383                            ExprCompiled::bin_op(Builtin2::Sub, l, r, &mut self.opt_ctx())
1384                        }
1385                        BinOp::Add => {
1386                            ExprCompiled::bin_op(Builtin2::Add, l, r, &mut self.opt_ctx())
1387                        }
1388                        BinOp::Multiply => {
1389                            ExprCompiled::bin_op(Builtin2::Multiply, l, r, &mut self.opt_ctx())
1390                        }
1391                        BinOp::Percent => {
1392                            ExprCompiled::bin_op(Builtin2::Percent, l, r, &mut self.opt_ctx())
1393                        }
1394                        BinOp::Divide => {
1395                            ExprCompiled::bin_op(Builtin2::Divide, l, r, &mut self.opt_ctx())
1396                        }
1397                        BinOp::FloorDivide => {
1398                            ExprCompiled::bin_op(Builtin2::FloorDivide, l, r, &mut self.opt_ctx())
1399                        }
1400                        BinOp::BitAnd => {
1401                            ExprCompiled::bin_op(Builtin2::BitAnd, l, r, &mut self.opt_ctx())
1402                        }
1403                        BinOp::BitOr => {
1404                            ExprCompiled::bin_op(Builtin2::BitOr, l, r, &mut self.opt_ctx())
1405                        }
1406                        BinOp::BitXor => {
1407                            ExprCompiled::bin_op(Builtin2::BitXor, l, r, &mut self.opt_ctx())
1408                        }
1409                        BinOp::LeftShift => {
1410                            ExprCompiled::bin_op(Builtin2::LeftShift, l, r, &mut self.opt_ctx())
1411                        }
1412                        BinOp::RightShift => {
1413                            ExprCompiled::bin_op(Builtin2::RightShift, l, r, &mut self.opt_ctx())
1414                        }
1415                    }
1416                }
1417            }
1418            ExprP::ListComprehension(x, for_, clauses) => {
1419                self.list_comprehension(x, for_, clauses)?
1420            }
1421            ExprP::DictComprehension(k_v, for_, clauses) => {
1422                let (k, v) = &**k_v;
1423                self.dict_comprehension(k, v, for_, clauses)?
1424            }
1425            ExprP::Literal(x) => {
1426                let val = x.compile(self.eval.module_env.frozen_heap());
1427                ExprCompiled::Value(val)
1428            }
1429            ExprP::FString(fstring) => {
1430                let Spanned {
1431                    node:
1432                        FStringP {
1433                            format,
1434                            expressions,
1435                        },
1436                    span: fstring_span,
1437                } = fstring;
1438
1439                let fstring_span = FrameSpan::new(FrozenFileSpan::new(self.codemap, *fstring_span));
1440
1441                // Desugar f"foo{x}bar{y}" to "foo{}bar{}.format(x, y)"
1442                let heap = self.eval.module_env.frozen_heap();
1443
1444                let format = IrSpanned {
1445                    node: ExprCompiled::Value(heap.alloc(format.node.as_str())),
1446                    span: fstring_span,
1447                };
1448                let method = IrSpanned {
1449                    node: ExprCompiled::dot(format, &Symbol::new("format"), &mut self.opt_ctx()),
1450                    span: fstring_span,
1451                };
1452
1453                let mut args = ArgsCompiledValue::default();
1454                for expr in expressions {
1455                    args.push_pos(self.expr(expr)?);
1456                }
1457
1458                CallCompiled::call(span, method, args, &mut self.opt_ctx())
1459            }
1460        };
1461        Ok(IrSpanned { node: expr, span })
1462    }
1463
1464    /// Like `expr` but returns an expression optimized assuming
1465    /// only the truth of the result is needed.
1466    pub(crate) fn expr_truth(
1467        &mut self,
1468        expr: &CstExpr,
1469    ) -> Result<IrSpanned<ExprCompiledBool>, CompilerInternalError> {
1470        let expr = self.expr(expr)?;
1471        Ok(ExprCompiledBool::new(expr))
1472    }
1473
1474    pub(crate) fn exprs(
1475        &mut self,
1476        exprs: &[CstExpr],
1477    ) -> Result<Vec<IrSpanned<ExprCompiled>>, CompilerInternalError> {
1478        exprs
1479            .iter()
1480            .map(|e| self.expr(e))
1481            .collect::<Result<_, CompilerInternalError>>()
1482    }
1483}