starlark/eval/compiler/
module.rs

1/*
2 * Copyright 2019 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//! Compile and evaluate module top-level statements.
19
20use starlark_syntax::eval_exception::EvalException;
21use starlark_syntax::syntax::ast::LoadP;
22use starlark_syntax::syntax::ast::StmtP;
23use starlark_syntax::syntax::top_level_stmts::top_level_stmts_mut;
24
25use crate::codemap::Spanned;
26use crate::const_frozen_string;
27use crate::eval::bc::frame::alloca_frame;
28use crate::eval::compiler::add_span_to_expr_error;
29use crate::eval::compiler::expr_throw;
30use crate::eval::compiler::scope::payload::CstPayload;
31use crate::eval::compiler::scope::payload::CstStmt;
32use crate::eval::compiler::scope::ScopeId;
33use crate::eval::compiler::scope::Slot;
34use crate::eval::compiler::Compiler;
35use crate::eval::runtime::frame_span::FrameSpan;
36use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
37use crate::typing::bindings::BindingsCollect;
38use crate::typing::error::InternalError;
39use crate::typing::fill_types_for_lint::ModuleVarTypes;
40use crate::typing::mode::TypecheckMode;
41use crate::typing::typecheck::solve_bindings;
42use crate::typing::Ty;
43use crate::typing::TypingOracleCtx;
44use crate::values::FrozenRef;
45use crate::values::FrozenStringValue;
46use crate::values::Value;
47
48#[derive(Debug, thiserror::Error)]
49enum ModuleError {
50    #[error("No imports are available, you tried `{0}` (no call to `Evaluator.set_loader`)")]
51    NoImportsAvailable(String),
52    #[error("Unexpected statement (internal error)")]
53    UnexpectedStatement,
54    #[error("Top level stmt count mismatch (internal error)")]
55    TopLevelStmtCountMismatch,
56}
57
58impl<'v> Compiler<'v, '_, '_, '_> {
59    fn eval_load(&mut self, load: Spanned<&LoadP<CstPayload>>) -> Result<(), EvalException> {
60        let name = &load.node.module.node;
61
62        let span = FrameSpan::new(FrozenFileSpan::new(self.codemap, load.span));
63
64        let loadenv = match self.eval.loader.as_ref() {
65            None => {
66                return Err(add_span_to_expr_error(
67                    crate::Error::new_other(ModuleError::NoImportsAvailable(name.to_owned())),
68                    span,
69                    self.eval,
70                ));
71            }
72            Some(loader) => expr_throw(loader.load(name), span, self.eval)?,
73        };
74
75        for load_arg in &load.node.args {
76            let (slot, _captured) = self
77                .scope_data
78                .get_assign_ident_slot(&load_arg.local, &self.codemap);
79            let slot = match slot {
80                Slot::Local(..) => unreachable!("symbol need to be resolved to module"),
81                Slot::Module(slot) => slot,
82            };
83            let value = expr_throw(
84                self.eval
85                    .module_env
86                    .load_symbol(&loadenv, &load_arg.their.node),
87                FrameSpan::new(FrozenFileSpan::new(self.codemap, load_arg.span())),
88                self.eval,
89            )?;
90            self.eval.set_slot_module(slot, value)
91        }
92
93        Ok(())
94    }
95
96    /// Compile and evaluate regular statement.
97    /// Regular statement is a statement which is not `load` or a sequence of statements.
98    fn eval_regular_top_level_stmt(
99        &mut self,
100        stmt: &mut CstStmt,
101        local_names: FrozenRef<'static, [FrozenStringValue]>,
102    ) -> Result<Value<'v>, EvalException> {
103        if matches!(stmt.node, StmtP::Statements(_) | StmtP::Load(_)) {
104            return Err(EvalException::new_anyhow(
105                ModuleError::UnexpectedStatement.into(),
106                stmt.span,
107                &self.codemap,
108            ));
109        }
110
111        let stmt = self
112            .module_top_level_stmt(stmt)
113            .map_err(|e| e.into_eval_exception())?;
114        let bc = stmt.as_bc(
115            &self.compile_context(false),
116            local_names,
117            0,
118            self.eval.module_env.frozen_heap(),
119        );
120        // We don't preserve locals between top level statements.
121        // That is OK for now: the only locals used in module evaluation
122        // are comprehension bindings.
123        let local_count = local_names.len().try_into().unwrap();
124        alloca_frame(
125            self.eval,
126            local_count,
127            bc.max_stack_size,
128            bc.max_loop_depth,
129            |eval| eval.eval_bc(const_frozen_string!("module").to_value(), &bc),
130        )
131    }
132
133    #[allow(clippy::mut_mut)] // Another false positive.
134    fn eval_top_level_stmt(
135        &mut self,
136        stmt: &mut CstStmt,
137        local_names: FrozenRef<'static, [FrozenStringValue]>,
138    ) -> Result<Value<'v>, EvalException> {
139        let mut stmts = top_level_stmts_mut(stmt);
140
141        if stmts.len() != self.top_level_stmt_count {
142            return Err(EvalException::new_anyhow(
143                ModuleError::TopLevelStmtCountMismatch.into(),
144                stmt.span,
145                &self.codemap,
146            ));
147        }
148
149        let mut last = Value::new_none();
150        for stmt in stmts.iter_mut() {
151            self.populate_types_in_stmt(stmt)?;
152
153            match &mut stmt.node {
154                StmtP::Load(load) => {
155                    self.eval_load(Spanned {
156                        node: load,
157                        span: stmt.span,
158                    })?;
159                    last = Value::new_none();
160                }
161                _ => last = self.eval_regular_top_level_stmt(stmt, local_names)?,
162            }
163        }
164
165        self.typecheck(&mut stmts)?;
166
167        Ok(last)
168    }
169
170    fn typecheck(&mut self, stmts: &mut [&mut CstStmt]) -> Result<(), EvalException> {
171        let typecheck = self.eval.static_typechecking || self.typecheck;
172        if !typecheck {
173            return Ok(());
174        }
175
176        let oracle = TypingOracleCtx {
177            codemap: &self.codemap,
178        };
179        let module_var_types = self.mk_module_var_types();
180        for top in stmts.iter_mut() {
181            if let StmtP::Def(_) = &mut top.node {
182                let BindingsCollect { bindings, .. } = BindingsCollect::collect_one(
183                    top,
184                    TypecheckMode::Compiler,
185                    &self.codemap,
186                    &mut Vec::new(),
187                )
188                .map_err(InternalError::into_eval_exception)?;
189                let (errors, ..) = match solve_bindings(bindings, oracle, &module_var_types) {
190                    Ok(x) => x,
191                    Err(e) => return Err(e.into_eval_exception()),
192                };
193
194                if let Some(error) = errors.into_iter().next() {
195                    return Err(error.into_eval_exception());
196                }
197            }
198        }
199
200        Ok(())
201    }
202
203    fn mk_module_var_types(&self) -> ModuleVarTypes {
204        let types = self
205            .eval
206            .module_env
207            .values_by_slot_id()
208            .into_iter()
209            .map(|(module_slot_id, value)| (module_slot_id, Ty::of_value(value)))
210            .collect();
211        ModuleVarTypes { types }
212    }
213
214    pub(crate) fn eval_module(
215        &mut self,
216        mut stmt: CstStmt,
217        local_names: FrozenRef<'static, [FrozenStringValue]>,
218    ) -> Result<Value<'v>, EvalException> {
219        self.enter_scope(ScopeId::module());
220        let value = self.eval_top_level_stmt(&mut stmt, local_names)?;
221        self.exit_scope();
222        assert!(self.locals.is_empty());
223        Ok(value)
224    }
225}