Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mut #692

Closed
wants to merge 7 commits into from
Closed

mut #692

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ pub enum NamedThing {
Variable {
name: String,
typ: Result<Type, TypeError>,
is_const: bool,
mutability: BindingMutability,
span: Span,
},
}
Expand Down Expand Up @@ -255,6 +255,13 @@ impl NamedThing {
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BindingMutability {
Mutable,
Immutable,
Const,
}

/// This should only be created by [`AnalyzerContext`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DiagnosticVoucher(PhantomData<()>);
Expand Down Expand Up @@ -386,15 +393,27 @@ pub struct ExpressionAttributes {
pub move_location: Option<Location>,
// Evaluated constant value of const local definition.
pub const_value: Option<Constant>,
pub mutable: bool,
}

impl ExpressionAttributes {
pub fn new(typ: Type, location: Location) -> Self {
pub fn new(typ: Type, location: Location, mutable: bool) -> Self {
Self {
typ,
location,
move_location: None,
const_value: None,
mutable,
}
}

pub fn immutable(typ: Type, location: Location) -> Self {
Self {
typ,
location,
move_location: None,
const_value: None,
mutable: false,
}
}

Expand Down
41 changes: 20 additions & 21 deletions crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::db::{Analysis, AnalyzerDb};
use crate::errors::TypeError;
use crate::namespace::items::{DepGraph, DepGraphWrapper, DepLocality, FunctionId, Item, TypeDef};
use crate::namespace::scopes::{BlockScope, BlockScopeType, FunctionScope, ItemScope};
use crate::namespace::types::{self, Contract, CtxDecl, SelfDecl, Struct, Type};
use crate::namespace::types::{self, Contract, CtxDecl, SelfDecl, SelfDeclKind, Struct, Type};
use crate::traversal::functions::traverse_statements;
use crate::traversal::types::type_desc;
use fe_common::diagnostics::Label;
Expand Down Expand Up @@ -35,15 +35,18 @@ pub fn function_signature(
.iter()
.enumerate()
.filter_map(|(index, arg)| match &arg.kind {
ast::FunctionArg::Self_ => {
ast::FunctionArg::Self_ { mut_ } => {
if fn_parent.is_none() {
scope.error(
"`self` can only be used in contract or struct functions",
arg.span,
"not allowed in functions defined outside of a contract or struct",
);
} else {
self_decl = Some(SelfDecl::Mutable);
self_decl = Some(SelfDecl {
kind: if mut_.is_some() { SelfDeclKind::MutRef } else { SelfDeclKind::Ref },
span: arg.span,
});
if index != 0 {
scope.error(
"`self` is not the first parameter",
Expand All @@ -54,12 +57,13 @@ pub fn function_signature(
}
None
}
ast::FunctionArg::Regular(reg) => {
let typ = type_desc(&mut scope, &reg.typ).and_then(|typ| match typ {

ast::FunctionArg::Regular { mut_, label, name, typ: typedesc } => {
let typ = type_desc(&mut scope, typedesc).and_then(|typ| match typ {
typ if typ.has_fixed_size() => Ok(typ),
_ => Err(TypeError::new(scope.error(
"function parameter types must have fixed size",
reg.typ.span,
typedesc.span,
"`Map` type can't be used as a function parameter",
))),
});
Expand All @@ -72,12 +76,6 @@ pub fn function_signature(
arg.span,
"instances of `Context` must be named `ctx`",
);
} else if !function.parent(db).is_contract() {
scope.error(
"`ctx` cannot be passed into pure functions",
arg.span,
"`ctx` can only be passed into contract functions",
);
} else if self_decl.is_some() && index != 1 {
scope.error(
"invalid parameter order",
Expand All @@ -96,7 +94,7 @@ pub fn function_signature(
}
}

if let Some(label) = &reg.label {
if let Some(label) = &label {
if_chain! {
if label.kind != "_";
if let Some(dup_idx) = labels.get(&label.kind);
Expand All @@ -115,30 +113,31 @@ pub fn function_signature(
}
}

if let Ok(Some(named_item)) = scope.resolve_name(&reg.name.kind, reg.name.span) {
if let Ok(Some(named_item)) = scope.resolve_name(&name.kind, name.span) {
scope.name_conflict_error(
"function parameter",
&reg.name.kind,
&name.kind,
&named_item,
named_item.name_span(db),
reg.name.span,
name.span,
);
None
} else if let Some(dup_idx) = names.get(&reg.name.kind) {
} else if let Some(dup_idx) = names.get(&name.kind) {
let dup_arg: &Node<ast::FunctionArg> = &def.args[*dup_idx];
scope.duplicate_name_error(
&format!("duplicate parameter names in function `{}`", def.name.kind),
&reg.name.kind,
&name.kind,
dup_arg.span,
arg.span,
);
None
} else {
names.insert(&reg.name.kind, index);
names.insert(&name.kind, index);
Some(types::FunctionParam::new(
reg.label.as_ref().map(|s| s.kind.as_str()),
&reg.name.kind,
label.as_ref().map(|s| s.kind.as_str()),
&name.kind,
typ,
mut_.is_some(),
))
}
}
Expand Down
23 changes: 10 additions & 13 deletions crates/analyzer/src/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::context;

use crate::context::{Analysis, Constant};
use crate::errors::{self, IncompleteItem, TypeError};
use crate::namespace::types::{self, GenericType};
use crate::namespace::types::{self, GenericType, SelfDecl};
use crate::traversal::pragma::check_pragma_version;
use crate::AnalyzerDb;
use crate::{builtins, errors::ConstEvalError};
Expand Down Expand Up @@ -758,7 +758,7 @@ impl TypeDef {
val.functions(db)
.iter()
.filter_map(|(name, field)| {
if field.takes_self(db) {
if field.self_decl(db).is_some() {
// In the future we probably want to resolve instance methods as well. But this would require
// the caller to pass an instance as the first argument e.g. `Rectangle::can_hold(self_instance, other)`.
// This isn't yet supported so for now path access to functions is limited to static functions only.
Expand Down Expand Up @@ -931,7 +931,7 @@ impl ContractId {
) -> Result<Option<Item>, IncompleteItem> {
if let Some(item) = self
.function(db, name)
.filter(|f| !f.takes_self(db))
.filter(|f| f.self_decl(db).is_none())
.map(Item::Function)
.or_else(|| self.event(db, name).map(Item::Event))
{
Expand Down Expand Up @@ -1094,17 +1094,14 @@ impl FunctionId {
self.data(db).module
}

pub fn takes_self(&self, db: &dyn AnalyzerDb) -> bool {
self.signature(db).self_decl.is_some()
pub fn self_decl(&self, db: &dyn AnalyzerDb) -> Option<SelfDecl> {
self.signature(db).self_decl
}
pub fn self_span(&self, db: &dyn AnalyzerDb) -> Option<Span> {
if self.takes_self(db) {
self.data(db)
.ast
.kind
.args
.iter()
.find_map(|arg| matches!(arg.kind, ast::FunctionArg::Self_).then(|| arg.span))
if self.self_decl(db).is_some() {
self.data(db).ast.kind.args.iter().find_map(|arg| {
matches!(arg.kind, ast::FunctionArg::Self_ { .. }).then(|| arg.span)
})
} else {
None
}
Expand Down Expand Up @@ -1159,7 +1156,7 @@ impl Class {
}
pub fn self_function(&self, db: &dyn AnalyzerDb, name: &str) -> Option<FunctionId> {
let fun = self.function(db, name)?;
fun.takes_self(db).then(|| fun)
fun.self_decl(db).map(|_| fun)
}

pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr {
Expand Down
35 changes: 26 additions & 9 deletions crates/analyzer/src/namespace/scopes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![allow(unstable_name_collisions)] // expect_none, which ain't gonna be stabilized

use crate::context::{
AnalyzerContext, CallType, Constant, ExpressionAttributes, FunctionBody, NamedThing,
AnalyzerContext, BindingMutability, CallType, Constant, ExpressionAttributes, FunctionBody,
NamedThing,
};
use crate::errors::{AlreadyDefined, IncompleteItem, TypeError};
use crate::namespace::items::{Class, EventId, FunctionId, ModuleId};
Expand Down Expand Up @@ -112,6 +113,8 @@ impl<'a> AnalyzerContext for ItemScope<'a> {
fn resolve_name(&self, name: &str, span: Span) -> Result<Option<NamedThing>, IncompleteItem> {
let item = self.module.resolve_name(self.db, name)?;

// TODO: do this somewhere else, so name resolution can be attempted
// without emitting an error
if let Some(item) = item {
check_item_visibility(self, item, span);
}
Expand Down Expand Up @@ -315,9 +318,13 @@ impl<'a> AnalyzerContext for FunctionScope<'a> {
.expect("found param type but not span");

NamedThing::Variable {
name: name.to_string(),
name: name.into(),
typ: param.typ.clone(),
is_const: false,
mutability: if param.is_mut {
BindingMutability::Mutable
} else {
BindingMutability::Immutable
},
span,
}
})
Expand Down Expand Up @@ -374,7 +381,7 @@ pub struct BlockScope<'a, 'b> {
pub root: &'a FunctionScope<'b>,
pub parent: Option<&'a BlockScope<'a, 'b>>,
/// Maps Name -> (Type, is_const, span)
pub variable_defs: BTreeMap<String, (Type, bool, Span)>,
pub variable_defs: BTreeMap<String, (Type, BindingMutability, Span)>,
pub constant_defs: RefCell<BTreeMap<String, Constant>>,
pub typ: BlockScopeType,
}
Expand All @@ -396,10 +403,10 @@ impl AnalyzerContext for BlockScope<'_, '_> {
if let Some(var) =
self.variable_defs
.get(name)
.map(|(typ, is_const, span)| NamedThing::Variable {
name: name.to_string(),
.map(|(typ, mutability, span)| NamedThing::Variable {
name: name.into(),
typ: Ok(typ.clone()),
is_const: *is_const,
mutability: *mutability,
span: *span,
})
{
Expand Down Expand Up @@ -513,12 +520,22 @@ impl<'a, 'b> BlockScope<'a, 'b> {
}
}

pub fn expr_is_mutable(&self, expr: &Node<Expr>) -> bool {
self.root
.body
.borrow()
.expressions
.get(&expr.id)
.unwrap()
.mutable
}

/// Add a variable to the block scope.
pub fn add_var(
&mut self,
name: &str,
typ: Type,
is_const: bool,
binding_kind: BindingMutability,
span: Span,
) -> Result<(), AlreadyDefined> {
match self.resolve_name(name, span) {
Expand Down Expand Up @@ -561,7 +578,7 @@ impl<'a, 'b> BlockScope<'a, 'b> {
}
_ => {
self.variable_defs
.insert(name.to_string(), (typ, is_const, span));
.insert(name.to_string(), (typ, binding_kind, span));
Ok(())
}
}
Expand Down
22 changes: 18 additions & 4 deletions crates/analyzer/src/namespace/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,16 @@ pub struct FunctionSignature {
pub return_type: Result<Type, TypeError>,
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum SelfDecl {
Mutable,
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SelfDecl {
pub kind: SelfDeclKind,
pub span: Span,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SelfDeclKind {
Ref,
MutRef,
// Value
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
Expand All @@ -164,13 +171,20 @@ pub enum CtxDecl {

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct FunctionParam {
pub is_mut: bool,
label: Option<SmolStr>,
pub name: SmolStr,
pub typ: Result<Type, TypeError>,
}
impl FunctionParam {
pub fn new(label: Option<&str>, name: &str, typ: Result<Type, TypeError>) -> Self {
pub fn new(
label: Option<&str>,
name: &str,
typ: Result<Type, TypeError>,
is_mut: bool,
) -> Self {
Self {
is_mut,
label: label.map(SmolStr::new),
name: name.into(),
typ,
Expand Down
Loading