Skip to content

Commit 262a20a

Browse files
authored
Merge pull request #536 from cburgdorf/christoph/feat/constants
Module level const support
2 parents 1e0711b + ae09c7a commit 262a20a

25 files changed

+391
-17
lines changed

crates/analyzer/src/db.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::context::{Analysis, FunctionBody};
22
use crate::errors::TypeError;
33
use crate::namespace::items::{
4-
self, ContractFieldId, ContractId, EventId, FunctionId, ModuleId, StructFieldId, StructId,
5-
TypeAliasId, TypeDefId,
4+
self, ContractFieldId, ContractId, EventId, FunctionId, ModuleConstantId, ModuleId,
5+
StructFieldId, StructId, TypeAliasId, TypeDefId,
66
};
77
use crate::namespace::types;
88
use indexmap::IndexMap;
@@ -31,6 +31,8 @@ pub trait AnalyzerDb {
3131
#[salsa::interned]
3232
fn intern_module(&self, data: Rc<items::Module>) -> ModuleId;
3333
#[salsa::interned]
34+
fn intern_module_const(&self, data: Rc<items::ModuleConstant>) -> ModuleConstantId;
35+
#[salsa::interned]
3436
fn intern_struct(&self, data: Rc<items::Struct>) -> StructId;
3537
#[salsa::interned]
3638
fn intern_struct_field(&self, data: Rc<items::StructField>) -> StructFieldId;
@@ -61,6 +63,15 @@ pub trait AnalyzerDb {
6163
fn module_contracts(&self, module: ModuleId) -> Rc<Vec<ContractId>>;
6264
#[salsa::invoke(queries::module::module_structs)]
6365
fn module_structs(&self, module: ModuleId) -> Rc<Vec<StructId>>;
66+
#[salsa::invoke(queries::module::module_constants)]
67+
fn module_constants(&self, module: ModuleId) -> Rc<Vec<ModuleConstantId>>;
68+
69+
// Module Constant
70+
#[salsa::invoke(queries::module::module_constant_type)]
71+
fn module_constant_type(
72+
&self,
73+
id: ModuleConstantId,
74+
) -> Analysis<Result<types::Type, TypeError>>;
6475

6576
// Contract
6677
#[salsa::invoke(queries::contracts::contract_all_functions)]

crates/analyzer/src/db/queries/functions.rs

+31-4
Original file line numberDiff line numberDiff line change
@@ -154,20 +154,47 @@ pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis<Rc<F
154154
}
155155
}
156156

157+
let mut block_scope = BlockScope::new(&scope, BlockScopeType::Function);
158+
159+
add_module_constants_to_scope(db, &mut block_scope);
160+
157161
// If `traverse_statements` fails, we can be confident that a diagnostic
158162
// has been emitted, either while analyzing this fn body or while analyzing
159163
// a type or fn used in this fn body, because of the `DiagnosticVoucher`
160164
// system. (See the definition of `FatalError`)
161-
let _ = traverse_statements(
162-
&mut BlockScope::new(&scope, BlockScopeType::Function),
163-
&def.body,
164-
);
165+
let _ = traverse_statements(&mut block_scope, &def.body);
165166
Analysis {
166167
value: Rc::new(scope.body.into_inner()),
167168
diagnostics: Rc::new(scope.diagnostics.into_inner()),
168169
}
169170
}
170171

172+
fn add_module_constants_to_scope(db: &dyn AnalyzerDb, scope: &mut BlockScope) {
173+
for module_const in scope.root.function.module(db).all_constants(db).iter() {
174+
let typ = match module_const.typ(db) {
175+
Ok(typ) => typ,
176+
Err(_) => {
177+
// No need to report module_constant_type() already reports this
178+
continue;
179+
}
180+
};
181+
182+
let const_type: FixedSize = match typ.try_into() {
183+
Ok(typ) => typ,
184+
Err(_) => {
185+
// No need to report module_constant_type() already reports this
186+
continue;
187+
}
188+
};
189+
190+
let const_name = module_const.name(db);
191+
// add_var emits a msg on err; we can ignore the Result.
192+
scope
193+
.add_var(&const_name, const_type, module_const.span(db))
194+
.ok();
195+
}
196+
}
197+
171198
fn all_paths_return_or_revert(block: &[Node<ast::FuncStmt>]) -> bool {
172199
for statement in block.iter().rev() {
173200
match &statement.kind {

crates/analyzer/src/db/queries/module.rs

+48-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use crate::builtins;
2-
use crate::context::Analysis;
2+
use crate::context::{Analysis, AnalyzerContext};
33
use crate::db::AnalyzerDb;
4-
use crate::errors;
4+
use crate::errors::{self, TypeError};
55
use crate::namespace::items::{
6-
Contract, ContractId, ModuleId, Struct, StructId, TypeAlias, TypeDefId,
6+
Contract, ContractId, ModuleConstant, ModuleConstantId, ModuleId, Struct, StructId, TypeAlias,
7+
TypeDefId,
78
};
8-
use crate::namespace::types;
9+
use crate::namespace::scopes::ItemScope;
10+
use crate::namespace::types::{self, Type};
11+
use crate::traversal::types::type_desc;
912
use fe_common::diagnostics::Label;
1013
use fe_parser::ast;
1114
use indexmap::map::{Entry, IndexMap};
@@ -131,3 +134,44 @@ pub fn module_structs(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<Vec<StructId>
131134
.collect(),
132135
)
133136
}
137+
138+
pub fn module_constants(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<Vec<ModuleConstantId>> {
139+
let ast::Module { body } = &module.data(db).ast;
140+
let ids = body
141+
.iter()
142+
.filter_map(|stmt| match stmt {
143+
ast::ModuleStmt::Constant(node) => {
144+
Some(db.intern_module_const(Rc::new(ModuleConstant {
145+
ast: node.clone(),
146+
module,
147+
})))
148+
}
149+
_ => None,
150+
})
151+
.collect();
152+
Rc::new(ids)
153+
}
154+
155+
pub fn module_constant_type(
156+
db: &dyn AnalyzerDb,
157+
constant: ModuleConstantId,
158+
) -> Analysis<Result<types::Type, TypeError>> {
159+
let mut scope = ItemScope::new(db, constant.data(db).module);
160+
let typ = type_desc(&mut scope, &constant.data(db).ast.kind.typ);
161+
162+
match &typ {
163+
Ok(typ) if !matches!(typ, Type::Base(_)) => {
164+
scope.error(
165+
"Non-base types not yet supported for constants",
166+
constant.data(db).ast.kind.typ.span,
167+
&format!("this has type `{}`; expected a primitive type", typ),
168+
);
169+
}
170+
_ => {}
171+
}
172+
173+
Analysis {
174+
value: typ,
175+
diagnostics: Rc::new(scope.diagnostics),
176+
}
177+
}

crates/analyzer/src/namespace/items.rs

+70
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::traversal::pragma::check_pragma_version;
77
use crate::AnalyzerDb;
88
use fe_common::diagnostics::Diagnostic;
99
use fe_parser::ast;
10+
use fe_parser::ast::Expr;
1011
use fe_parser::node::{Node, Span};
1112
use indexmap::IndexMap;
1213
use std::rc::Rc;
@@ -71,6 +72,19 @@ impl ModuleId {
7172
db.module_structs(*self)
7273
}
7374

75+
/// All constants, including duplicates
76+
pub fn all_constants(&self, db: &dyn AnalyzerDb) -> Rc<Vec<ModuleConstantId>> {
77+
db.module_constants(*self)
78+
}
79+
80+
/// Get constant by name
81+
pub fn lookup_constant(&self, db: &dyn AnalyzerDb, name: &str) -> Option<ModuleConstantId> {
82+
self.all_constants(db)
83+
.iter()
84+
.cloned()
85+
.find(|item| item.name(db) == name)
86+
}
87+
7488
pub fn diagnostics(&self, db: &dyn AnalyzerDb) -> Vec<Diagnostic> {
7589
let mut diagnostics = vec![];
7690
self.sink_diagnostics(db, &mut diagnostics);
@@ -89,6 +103,10 @@ impl ModuleId {
89103
ast::ModuleStmt::Use(inner) => {
90104
sink.push(&errors::not_yet_implemented("use", inner.span));
91105
}
106+
ast::ModuleStmt::Constant(_) => self
107+
.all_constants(db)
108+
.iter()
109+
.for_each(|id| id.sink_diagnostics(db, sink)),
92110
_ => {} // everything else is a type def, handled below.
93111
}
94112
}
@@ -103,6 +121,58 @@ impl ModuleId {
103121
}
104122
}
105123

124+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
125+
pub struct ModuleConstant {
126+
pub ast: Node<ast::ConstantDecl>,
127+
pub module: ModuleId,
128+
}
129+
130+
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
131+
pub struct ModuleConstantId(pub(crate) u32);
132+
impl_intern_key!(ModuleConstantId);
133+
134+
impl ModuleConstantId {
135+
pub fn data(&self, db: &dyn AnalyzerDb) -> Rc<ModuleConstant> {
136+
db.lookup_intern_module_const(*self)
137+
}
138+
pub fn span(&self, db: &dyn AnalyzerDb) -> Span {
139+
self.data(db).ast.span
140+
}
141+
pub fn typ(&self, db: &dyn AnalyzerDb) -> Result<types::Type, TypeError> {
142+
db.module_constant_type(*self).value
143+
}
144+
145+
pub fn is_base_type(&self, db: &dyn AnalyzerDb) -> bool {
146+
matches!(self.typ(db), Ok(types::Type::Base(_)))
147+
}
148+
149+
pub fn name(&self, db: &dyn AnalyzerDb) -> String {
150+
self.data(db).ast.kind.name.kind.clone()
151+
}
152+
153+
pub fn value(&self, db: &dyn AnalyzerDb) -> fe_parser::ast::Expr {
154+
self.data(db).ast.kind.value.kind.clone()
155+
}
156+
157+
pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) {
158+
db.module_constant_type(*self)
159+
.diagnostics
160+
.iter()
161+
.for_each(|d| sink.push(d));
162+
163+
if !matches!(
164+
self.value(db),
165+
Expr::Bool(_) | Expr::Num(_) | Expr::Str(_) | Expr::Unit
166+
) {
167+
sink.push(&errors::error(
168+
"non-literal expressions not yet supported for constants",
169+
self.data(db).ast.kind.value.span,
170+
"not a literal",
171+
))
172+
}
173+
}
174+
}
175+
106176
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
107177
pub enum TypeDefId {
108178
Alias(TypeAliasId),

crates/analyzer/tests/errors.rs

+3
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ test_file! { mismatch_return_type }
219219
test_file! { missing_return }
220220
test_file! { missing_return_in_else }
221221
test_file! { missing_return_after_if }
222+
test_file! { module_const_unknown_type }
223+
test_file! { module_const_non_base_type }
224+
test_file! { module_const_not_literal }
222225
test_file! { needs_mem_copy }
223226
test_file! { not_callable }
224227
test_file! { not_in_scope }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/analyzer/tests/errors.rs
3+
expression: "error_string(&path, &src)"
4+
5+
---
6+
error: Non-base types not yet supported for constants
7+
┌─ compile_errors/module_const_non_base_type.fe:1:12
8+
9+
1const FOO: String<3> = "foo"
10+
^^^^^^^^^ this has type `String<3>`; expected a primitive type
11+
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/analyzer/tests/errors.rs
3+
expression: "error_string(&path, &src)"
4+
5+
---
6+
error: non-literal expressions not yet supported for constants
7+
┌─ compile_errors/module_const_not_literal.fe:1:19
8+
9+
1const FOO: u256 = 1 + 2
10+
^^^^^ not a literal
11+
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/analyzer/tests/errors.rs
3+
expression: "error_string(&path, &src)"
4+
5+
---
6+
error: undefined type
7+
┌─ compile_errors/module_const_unknown_type.fe:1:12
8+
9+
1const FOO: Bar = 1
10+
^^^ this type name has not been defined
11+
12+

crates/lowering/src/context.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use fe_analyzer::context::{ExpressionAttributes, FunctionBody};
2+
use fe_analyzer::namespace::items::FunctionId;
23
use fe_analyzer::namespace::types::{Array, FixedSize, Tuple};
34
use fe_analyzer::AnalyzerDb;
45
use fe_parser::ast;
@@ -52,16 +53,22 @@ impl<'a, 'db> ContractContext<'a, 'db> {
5253
pub struct FnContext<'a, 'b, 'db> {
5354
pub contract: &'a mut ContractContext<'b, 'db>,
5455
pub body: Rc<FunctionBody>,
56+
pub id: FunctionId,
5557

5658
/// Holds fresh id for [`FnContext::make_unique_name`]
5759
fresh_id: u64,
5860
}
5961

6062
impl<'a, 'b, 'db> FnContext<'a, 'b, 'db> {
61-
pub fn new(contract: &'a mut ContractContext<'b, 'db>, body: Rc<FunctionBody>) -> Self {
63+
pub fn new(
64+
id: FunctionId,
65+
contract: &'a mut ContractContext<'b, 'db>,
66+
body: Rc<FunctionBody>,
67+
) -> Self {
6268
Self {
6369
contract,
6470
body,
71+
id,
6572
fresh_id: 0,
6673
}
6774
}

crates/lowering/src/mappers/expressions.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub fn expr(context: &mut FnContext, exp: Node<fe::Expr>) -> Node<fe::Expr> {
1010
let span = exp.span;
1111

1212
let lowered_kind = match exp.kind {
13-
fe::Expr::Name(_) => exp.kind,
13+
fe::Expr::Name(_) => expr_name(context, exp),
1414
fe::Expr::Num(_) => exp.kind,
1515
fe::Expr::Bool(_) => exp.kind,
1616
fe::Expr::Subscript { value, index } => fe::Expr::Subscript {
@@ -103,6 +103,27 @@ pub fn call_args(
103103
Node::new(lowered_args, args.span)
104104
}
105105

106+
fn expr_name(context: &mut FnContext, exp: Node<fe::Expr>) -> fe::Expr {
107+
let name = match &exp.kind {
108+
fe::Expr::Name(name) => name,
109+
_ => unreachable!(),
110+
};
111+
112+
let db = context.db();
113+
114+
let module_const = context.id.module(db).lookup_constant(db, name);
115+
116+
// If the name maps to a base type constant we lower the code to inline
117+
// the name expression to the value expression of the constant.
118+
match module_const {
119+
Some(val) if val.is_base_type(db) => val.value(db),
120+
Some(_) => {
121+
panic!("Should have been rejected at first analyzer pass")
122+
}
123+
None => exp.kind,
124+
}
125+
}
126+
106127
fn expr_tuple(context: &mut FnContext, exp: Node<fe::Expr>) -> fe::Expr {
107128
let typ = context
108129
.expression_attributes(&exp)

crates/lowering/src/mappers/functions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use fe_parser::node::Node;
1111

1212
/// Lowers a function definition.
1313
pub fn func_def(contract: &mut ContractContext, function: FunctionId) -> Node<fe::Function> {
14-
let mut context = FnContext::new(contract, function.body(contract.db()));
14+
let mut context = FnContext::new(function, contract, function.body(contract.db()));
1515
let node = &function.data(context.db()).ast;
1616
let fe::Function {
1717
is_pub,

crates/lowering/src/mappers/module.rs

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub fn module(db: &dyn AnalyzerDb, module: ModuleId) -> ast::Module {
2020
.filter_map(|stmt| match stmt {
2121
ast::ModuleStmt::Pragma(_) => Some(stmt.clone()),
2222
ast::ModuleStmt::Use(_) => Some(stmt.clone()),
23+
// All name expressions referring to constants are handled at the time of lowering,
24+
// which causes the constants to no longer serve a purpose.
25+
ast::ModuleStmt::Constant(_) => None,
2326
_ => None,
2427
})
2528
.collect::<Vec<_>>();

crates/lowering/tests/lowering.rs

+1
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,6 @@ test_file! { nested_tuple, "lowering/nested_tuple.fe" }
6767
test_file! { map_tuple, "lowering/map_tuple.fe" }
6868
test_file! { type_alias_tuple, "lowering/type_alias_tuple.fe" }
6969
test_file! { tuple_destruct, "lowering/tuple_destruct.fe" }
70+
test_file! { module_const, "lowering/module_const.fe" }
7071
// TODO: the analyzer rejects lowered nested tuples.
7172
// test_file!(array_tuple, "lowering/array_tuple.fe");

0 commit comments

Comments
 (0)