Skip to content

Commit 3925ca1

Browse files
stepanchegfacebook-github-bot
authored andcommitted
Store module private variables in module, not in locals
Summary: When loaded symbols are no longer reexported (D30534539), module private symbols (e. g. loaded symbols) are stored in local stack. This diff changes privates to store them in module itself (like with public module variables). The reasons for this diff: * eventually replace `ValueRef` with explicit cell objects * to implement loaded constants propagation This diff it useful as is because it improves error message on trying to import private symbols. For example, for this code: ``` # a.bzl x = 1 # b.bzl load("a.bzl", "x") # c.bzl load("b.bzl", "x") ``` Previously error message was `Variable 'x' not found`, now error message is `Module symbol 'x' is not exported`. Reviewed By: ndmitchell Differential Revision: D30561707 fbshipit-source-id: 309948560569f48f00e520424edaf1a197c0ff70
1 parent 2d0e222 commit 3925ca1

File tree

8 files changed

+92
-48
lines changed

8 files changed

+92
-48
lines changed

starlark/src/debug/evaluate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl<'v, 'a> Evaluator<'v, 'a> {
4949
// Push all the frozen variables into the module
5050
if let Some(frozen) = &self.module_variables {
5151
for (name, slot) in frozen.0.names.symbols() {
52-
if let Some(value) = frozen.0.get_slot(*slot) {
52+
if let Some(value) = frozen.0.get_slot(slot) {
5353
self.module_env.set(name, value.to_value())
5454
}
5555
}

starlark/src/environment/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ pub(crate) enum EnvironmentError {
4242
/// Cannot import private symbol, i.e. underscore prefixed
4343
#[error("Cannot import private symbol `{0}`")]
4444
CannotImportPrivateSymbol(String),
45+
#[error("Module has no symbol `{0}`")]
46+
ModuleHasNoSymbol(String),
47+
#[error("Module symbol `{0}` is not exported")]
48+
ModuleSymbolIsNotExported(String),
4549
#[error("No imports are available, you tried `{0}` (no call to `Evaluator.set_loader`)")]
4650
NoImportsAvailable(String),
4751
}

starlark/src/environment/modules.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{
2626
slots::{FrozenSlots, ModuleSlotId, MutableSlots},
2727
EnvironmentError,
2828
},
29+
syntax::ast::Visibility,
2930
values::{
3031
docs,
3132
docs::{DocItem, DocString},
@@ -100,16 +101,25 @@ pub struct Module {
100101
}
101102

102103
impl FrozenModule {
103-
/// Get the value of the variable `name`.
104-
/// Returns [`None`] if the variable isn't defined in the module or hasn't been set.
105-
pub fn get(&self, name: &str) -> Option<OwnedFrozenValue> {
106-
let slot = self.1.0.names.get_name(name)?;
104+
/// Get value, exported or private by name.
105+
pub(crate) fn get_any_visibility(&self, name: &str) -> Option<(OwnedFrozenValue, Visibility)> {
106+
let (slot, vis) = self.1.0.names.get_name(name)?;
107107
// This code is safe because we know the frozen module ref keeps the values alive
108108
self.1
109109
.0
110110
.slots
111111
.get_slot(slot)
112-
.map(|x| unsafe { OwnedFrozenValue::new(self.0.dupe(), x) })
112+
.map(|x| (unsafe { OwnedFrozenValue::new(self.0.dupe(), x) }, vis))
113+
}
114+
115+
/// Get the value of the exported variable `name`.
116+
/// Returns [`None`] if the variable isn't defined in the module or it is private.
117+
pub fn get(&self, name: &str) -> Option<OwnedFrozenValue> {
118+
self.get_any_visibility(name)
119+
.and_then(|(value, vis)| match vis {
120+
Visibility::Private => None,
121+
Visibility::Public => Some(value),
122+
})
113123
}
114124

115125
/// Iterate through all the names defined in this module.
@@ -157,7 +167,7 @@ impl FrozenModuleData {
157167
pub fn describe(&self) -> String {
158168
self.names
159169
.symbols()
160-
.filter_map(|(name, slot)| Some((name, self.slots.get_slot(*slot)?)))
170+
.filter_map(|(name, slot)| Some((name, self.slots.get_slot(slot)?)))
161171
.map(|(name, val)| val.to_value().describe(name))
162172
.join("\n")
163173
}
@@ -170,7 +180,7 @@ impl FrozenModuleData {
170180
/// Inefficient - only use in error paths.
171181
pub(crate) fn get_slot_name(&self, slot: ModuleSlotId) -> Option<String> {
172182
for (s, i) in self.names.symbols() {
173-
if *i == slot {
183+
if i == slot {
174184
return Some(s.clone());
175185
}
176186
}
@@ -248,10 +258,21 @@ impl Module {
248258
unsafe { transmute!(&'v MutableSlots<'static>, &'v MutableSlots<'v>, &self.slots) }
249259
}
250260

251-
/// Get the value of the variable `name`, or [`None`] if the variable is not defined.
261+
/// Get value, exported or private by name.
262+
pub(crate) fn get_any_visibility<'v>(&'v self, name: &str) -> Option<(Value<'v>, Visibility)> {
263+
let (slot, vis) = self.names.get_name(name)?;
264+
let value = self.slots().get_slot(slot)?;
265+
Some((value, vis))
266+
}
267+
268+
/// Get the value of the exported variable `name`.
269+
/// Returns [`None`] if the variable isn't defined in the module or it is private.
252270
pub fn get<'v>(&'v self, name: &str) -> Option<Value<'v>> {
253-
let slot = self.names.get_name(name)?;
254-
self.slots().get_slot(slot)
271+
self.get_any_visibility(name)
272+
.and_then(|(v, vis)| match vis {
273+
Visibility::Private => None,
274+
Visibility::Public => Some(v),
275+
})
255276
}
256277

257278
/// Freeze the environment, all its value will become immutable afterwards.
@@ -301,7 +322,7 @@ impl Module {
301322
self.frozen_heap.add_reference(&module.0);
302323
for (k, slot) in module.1.0.names.symbols() {
303324
if Self::is_public_symbol(k) {
304-
if let Some(value) = module.1.0.slots.get_slot(*slot) {
325+
if let Some(value) = module.1.0.slots.get_slot(slot) {
305326
self.set(k, Value::new_frozen(value))
306327
}
307328
}
@@ -316,9 +337,12 @@ impl Module {
316337
if !Self::is_public_symbol(symbol) {
317338
return Err(EnvironmentError::CannotImportPrivateSymbol(symbol.to_owned()).into());
318339
}
319-
match module.get(symbol) {
320-
None => Err(EnvironmentError::VariableNotFound(symbol.to_owned()).into()),
321-
Some(v) => Ok(v.owned_value(self.frozen_heap())),
340+
match module.get_any_visibility(symbol) {
341+
None => Err(EnvironmentError::ModuleHasNoSymbol(symbol.to_owned()).into()),
342+
Some((v, Visibility::Public)) => Ok(v.owned_value(self.frozen_heap())),
343+
Some((_, Visibility::Private)) => {
344+
Err(EnvironmentError::ModuleSymbolIsNotExported(symbol.to_owned()).into())
345+
}
322346
}
323347
}
324348

starlark/src/environment/names.rs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
use std::{cell::RefCell, collections::HashMap, iter::Iterator};
1919

20-
use crate::environment::slots::ModuleSlotId;
20+
use crate::{environment::slots::ModuleSlotId, syntax::ast::Visibility};
2121

2222
/// MutableNames are how we allocate slots (index-based) to variables
2323
/// (name-based). The slots field is the current active mapping of names to
@@ -37,10 +37,10 @@ use crate::environment::slots::ModuleSlotId;
3737
/// On an unscope, we do the reverse, putting things back to how they were
3838
/// before (apart from the total) number of slots required.
3939
#[derive(Debug)]
40-
pub(crate) struct MutableNames(RefCell<HashMap<String, ModuleSlotId>>);
40+
pub(crate) struct MutableNames(RefCell<HashMap<String, (ModuleSlotId, Visibility)>>);
4141

4242
#[derive(Debug)]
43-
pub(crate) struct FrozenNames(HashMap<String, ModuleSlotId>);
43+
pub(crate) struct FrozenNames(HashMap<String, (ModuleSlotId, Visibility)>);
4444

4545
impl MutableNames {
4646
pub fn new() -> Self {
@@ -54,37 +54,52 @@ impl MutableNames {
5454
/// Try and go back from a slot to a name.
5555
/// Inefficient - only use in error paths.
5656
pub fn get_slot(&self, slot: ModuleSlotId) -> Option<String> {
57-
for (s, i) in &*self.0.borrow() {
57+
for (s, (i, _vis)) in &*self.0.borrow() {
5858
if *i == slot {
5959
return Some(s.clone());
6060
}
6161
}
6262
None
6363
}
6464

65-
pub fn get_name(&self, name: &str) -> Option<ModuleSlotId> {
65+
pub(crate) fn get_name(&self, name: &str) -> Option<(ModuleSlotId, Visibility)> {
6666
self.0.borrow().get(name).copied()
6767
}
6868

69-
// Add a name, or if it's already there, return the existing name
70-
pub fn add_name(&self, name: &str) -> ModuleSlotId {
69+
/// Add a name with explicit visibility to the module.
70+
pub(crate) fn add_name_visibility(&self, name: &str, vis: Visibility) -> ModuleSlotId {
7171
let mut x = self.0.borrow_mut();
72-
match x.get(name) {
73-
Some(v) => *v,
72+
match x.get_mut(name) {
73+
Some((slot, stored_vis)) => {
74+
// Public visibility wins.
75+
if *stored_vis == Visibility::Private {
76+
*stored_vis = vis;
77+
}
78+
*slot
79+
}
7480
None => {
7581
let slot = ModuleSlotId::new(x.len());
76-
x.insert(name.to_owned(), slot);
82+
x.insert(name.to_owned(), (slot, vis));
7783
slot
7884
}
7985
}
8086
}
8187

88+
// Add an exported name, or if it's already there, return the existing name
89+
pub fn add_name(&self, name: &str) -> ModuleSlotId {
90+
self.add_name_visibility(name, Visibility::Public)
91+
}
92+
8293
pub fn hide_name(&self, name: &str) {
8394
self.0.borrow_mut().remove(name);
8495
}
8596

8697
pub fn all_names(&self) -> HashMap<String, ModuleSlotId> {
87-
self.0.borrow().clone()
98+
self.0
99+
.borrow()
100+
.iter()
101+
.map(|(name, (slot, _vis))| (name.to_owned(), *slot))
102+
.collect()
88103
}
89104

90105
pub fn freeze(self) -> FrozenNames {
@@ -93,11 +108,15 @@ impl MutableNames {
93108
}
94109

95110
impl FrozenNames {
96-
pub fn get_name(&self, name: &str) -> Option<ModuleSlotId> {
111+
pub(crate) fn get_name(&self, name: &str) -> Option<(ModuleSlotId, Visibility)> {
97112
self.0.get(name).copied()
98113
}
99114

100-
pub fn symbols(&self) -> impl Iterator<Item = (&String, &ModuleSlotId)> {
101-
self.0.iter()
115+
/// Exported symbols.
116+
pub fn symbols(&self) -> impl Iterator<Item = (&String, ModuleSlotId)> {
117+
self.0.iter().flat_map(|(name, (slot, vis))| match vis {
118+
Visibility::Private => None,
119+
Visibility::Public => Some((name, *slot)),
120+
})
102121
}
103122
}

starlark/src/eval/compiler/scope.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use crate::{
1919
environment::{names::MutableNames, slots::ModuleSlotId},
2020
eval::runtime::slots::LocalSlotId,
21-
syntax::ast::{AstAssign, AstStmt, Expr, Stmt, Visibility},
21+
syntax::ast::{AstAssign, AstStmt, Expr, Stmt},
2222
};
2323
use std::collections::HashMap;
2424

@@ -112,20 +112,12 @@ impl<'a> Scope<'a> {
112112
pub fn enter_module(module: &'a MutableNames, code: &AstStmt) -> Self {
113113
let mut locals = HashMap::new();
114114
Stmt::collect_defines(code, &mut locals);
115-
let mut module_private = ScopeNames::default();
116115
for (x, vis) in locals {
117-
match vis {
118-
Visibility::Public => {
119-
module.add_name(x);
120-
}
121-
Visibility::Private => {
122-
module_private.add_name(x);
123-
}
124-
}
116+
module.add_name_visibility(x, vis);
125117
}
126118
Self {
127119
module,
128-
locals: vec![module_private],
120+
locals: vec![ScopeNames::default()],
129121
unscopes: Vec::new(),
130122
}
131123
}
@@ -195,7 +187,9 @@ impl<'a> Scope<'a> {
195187
return Some(Slot::Local(v));
196188
}
197189
}
198-
self.module.get_name(name).map(Slot::Module)
190+
self.module
191+
.get_name(name)
192+
.map(|(slot, _vis)| Slot::Module(slot))
199193
}
200194

201195
pub fn get_name_or_panic(&mut self, name: &str) -> Slot {

starlark/src/eval/fragment/stmt.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,10 @@ impl Compiler<'_> {
502502
let name = name.node;
503503
let symbols = v.into_map(|(x, y)| {
504504
(
505-
self.scope.get_name_or_panic(&x.node),
505+
match self.scope.get_name_or_panic(&x.node) {
506+
Slot::Local(..) => unreachable!("symbol need to be resolved to module"),
507+
Slot::Module(slot) => slot,
508+
},
506509
y.node,
507510
x.span.merge(y.span),
508511
)
@@ -522,10 +525,7 @@ impl Compiler<'_> {
522525
*span,
523526
eval,
524527
)?;
525-
match new_name {
526-
Slot::Local(slot) => eval.set_slot_local(*slot, value),
527-
Slot::Module(slot) => eval.set_slot_module(*slot, value),
528-
}
528+
eval.set_slot_module(*new_name, value)
529529
}
530530
})
531531
}

starlark/src/eval/tests.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1264,7 +1264,10 @@ fn test_load_reexport() {
12641264
a.dialect_set(|d| d.enable_load_reexport = false);
12651265
a.module("a", "x = 1");
12661266
a.module("b", "load('a', 'x')");
1267-
a.fail("load('b', 'x')\nassert_eq(x, 1)", "Variable `x` not found");
1267+
a.fail(
1268+
"load('b', 'x')\nassert_eq(x, 1)",
1269+
"Module symbol `x` is not exported",
1270+
);
12681271
}
12691272

12701273
#[test]

starlark/src/syntax/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub enum AssignOp {
180180
RightShift, // >>=
181181
}
182182

183-
#[derive(Debug, Copy, Clone, Dupe)]
183+
#[derive(Debug, Copy, Clone, Dupe, Eq, PartialEq)]
184184
pub enum Visibility {
185185
Private,
186186
Public,

0 commit comments

Comments
 (0)