Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sbillig committed Apr 4, 2022
1 parent 96bc925 commit 26cba7a
Show file tree
Hide file tree
Showing 89 changed files with 857 additions and 667 deletions.
25 changes: 19 additions & 6 deletions crates/analyzer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pub enum NamedThing {
Item(Item),
SelfValue {
/// Function `self` parameter.
decl: Option<SelfDecl>,
decl: Option<SelfDecl>, // XXX is this used?

/// The function's parent, if any. If `None`, `self` has been
/// used in a module-level function.
Expand All @@ -225,7 +225,7 @@ pub enum NamedThing {
Variable {
name: String,
typ: Result<FixedSize, TypeError>,
kind: NameBindingKind,
mutability: BindingMutability,
span: Span,
},
}
Expand Down Expand Up @@ -256,10 +256,10 @@ impl NamedThing {
}

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

/// This should only be created by [`AnalyzerContext`].
Expand Down Expand Up @@ -399,15 +399,28 @@ 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,
}
}

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

Expand Down
2 changes: 1 addition & 1 deletion crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub fn function_signature(
"not allowed in functions defined outside of a contract or struct",
);
} else {
self_decl = Some(if mut_.is_some() { SelfDecl::Mutable } else { SelfDecl::Immutable });
self_decl = Some(if mut_.is_some() { SelfDecl::MutRef } else { SelfDecl::Ref });
if index != 0 {
scope.error(
"`self` is not the first parameter",
Expand Down
26 changes: 18 additions & 8 deletions crates/analyzer/src/namespace/scopes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unstable_name_collisions)] // expect_none, which ain't gonna be stabilized

use crate::context::{
AnalyzerContext, CallType, Constant, ExpressionAttributes, FunctionBody, NameBindingKind,
AnalyzerContext, BindingMutability, CallType, Constant, ExpressionAttributes, FunctionBody,
NamedThing,
};
use crate::errors::{AlreadyDefined, IncompleteItem, TypeError};
Expand Down Expand Up @@ -334,10 +334,10 @@ impl<'a> AnalyzerContext for FunctionScope<'a> {
NamedThing::Variable {
name: name.to_string(),
typ: param.typ.clone(),
kind: if param.is_mut {
NameBindingKind::LetMut
mutability: if param.is_mut {
BindingMutability::Mutable
} else {
NameBindingKind::Let
BindingMutability::Immutable
},
span,
}
Expand Down Expand Up @@ -394,7 +394,7 @@ impl<'a> AnalyzerContext for FunctionScope<'a> {
pub struct BlockScope<'a, 'b> {
pub root: &'a FunctionScope<'b>,
pub parent: Option<&'a BlockScope<'a, 'b>>,
pub variable_defs: BTreeMap<String, (FixedSize, NameBindingKind, Span)>,
pub variable_defs: BTreeMap<String, (FixedSize, BindingMutability, Span)>,
pub constant_defs: RefCell<BTreeMap<String, Constant>>,
pub typ: BlockScopeType,
}
Expand All @@ -416,10 +416,10 @@ impl AnalyzerContext for BlockScope<'_, '_> {
if let Some(var) =
self.variable_defs
.get(name)
.map(|(typ, kind, span)| NamedThing::Variable {
.map(|(typ, mutability, span)| NamedThing::Variable {
name: name.to_string(),
typ: Ok(typ.clone()),
kind: *kind,
mutability: *mutability,
span: *span,
})
{
Expand Down Expand Up @@ -533,12 +533,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: FixedSize,
binding_kind: NameBindingKind,
binding_kind: BindingMutability,
span: Span,
) -> Result<(), AlreadyDefined> {
match self.resolve_name(name, span) {
Expand Down
9 changes: 7 additions & 2 deletions crates/analyzer/src/namespace/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@ pub struct FunctionSignature {

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum SelfDecl {
Mutable,
Immutable,
Ref,
MutRef,
// Value
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
Expand Down Expand Up @@ -465,6 +466,10 @@ impl Type {
matches!(self, Type::Base(Base::Numeric(_)))
}

pub fn is_base(&self) -> bool {
matches!(self, Type::Base(_))
}

pub fn is_signed_integer(&self) -> bool {
if let Type::Base(Base::Numeric(integer)) = &self {
return integer.is_signed();
Expand Down
183 changes: 103 additions & 80 deletions crates/analyzer/src/traversal/assignments.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::context::{AnalyzerContext, Location, NameBindingKind, NamedThing};
use crate::context::{AnalyzerContext, BindingMutability, Location, NamedThing};
use crate::errors::FatalError;
use crate::namespace::scopes::BlockScope;
use crate::operations;
Expand All @@ -12,118 +12,141 @@ use fe_parser::node::Node;
///
/// e.g. `foo[42] = "bar"`, `self.foo[42] = "bar"`, `foo = 42`
pub fn assign(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(), FatalError> {
if let fe::FuncStmt::Assign { target, value } = &stmt.kind {
let target_attributes = expressions::expr(scope, target, None)?;

let value_attributes = expressions::expr(scope, value, Some(&target_attributes.typ))?;
check_assign_target(scope, target)?;
if target_attributes.typ != value_attributes.typ {
scope.fancy_error(
"mismatched types",
vec![
Label::primary(
target.span,
format!("this variable has type `{}`", target_attributes.typ),
),
Label::secondary(
value.span,
format!(
"this value has incompatible type `{}`",
value_attributes.typ
),
),
],
vec![],
);
}
let (target, rhs) = match &stmt.kind {
fe::FuncStmt::Assign { target, value } => (target, value),
_ => unreachable!(),
};

if matches!(
(
target_attributes.location,
value_attributes.final_location(),
),
(Location::Memory, Location::Storage { .. })
) {
scope.fancy_error(
"location mismatch",
vec![
Label::primary(target.span, "this variable is located in memory"),
Label::secondary(value.span, "this value is located in storage"),
],
vec!["Hint: values located in storage can be copied to memory using the `to_mem` function.".into(),
"Example: `self.my_array.to_mem()`".into(),
],
);
}
let target_attributes = expressions::expr(scope, target, None)?;
let rhs_attributes = expressions::expr(scope, rhs, Some(&target_attributes.typ))?;

check_assign_target(scope, target)?;

if target_attributes.typ != rhs_attributes.typ {
scope.fancy_error(
"mismatched types",
vec![
Label::primary(
target.span,
format!("this has type `{}`", target_attributes.typ),
),
Label::secondary(
rhs.span,
format!("this value has incompatible type `{}`", rhs_attributes.typ),
),
],
vec![],
);
}

if target_attributes.mutable && !rhs_attributes.mutable {
// XXX
// scope.fancy_error(
// "sneaky mutation", // XXX better error
// vec![
// Label::primary(target.span, "this is mutable"),
// Label::secondary(value.span, "this is immutable"),
// ],
// vec![],
// );
}

return Ok(());
if matches!(
(target_attributes.location, rhs_attributes.final_location(),),
(Location::Memory, Location::Storage { .. })
) {
scope.fancy_error(
"location mismatch",
vec![
Label::primary(target.span, "this variable is located in memory"),
Label::secondary(rhs.span, "this value is located in storage"),
],
vec!["Hint: values located in storage can be copied to memory using the `to_mem` function.".into(),
"Example: `self.my_array.to_mem()`".into(),
],
);
}

unreachable!()
Ok(())
}

fn check_assign_target(scope: &mut BlockScope, expr: &Node<fe::Expr>) -> Result<(), FatalError> {
fn check_assign_target(scope: &mut BlockScope, target: &Node<fe::Expr>) -> Result<(), FatalError> {
use fe::Expr::*;

match &expr.kind {
Attribute { .. } | Subscript { .. } => Ok(()),
match &target.kind {
Attribute { value: container, .. } | Subscript { value: container, .. } => {
if !scope.expr_is_mutable(&container) {
scope.fancy_error("immutable", // XXX better error
vec![Label::primary(container.span, "")],
vec![]);
}
// XXX
// if container_attr.mutable && !scope.expr_attributes(rhs).mutable {
// scope.fancy_error("can't assign mut ref to non-mut ref", // XXX better error
// vec![Label::primary(container.span, "")],
// vec![]);
// }
Ok(())
}
Tuple { elts } => {
for elt in elts {
check_assign_target(scope, elt)?;
}
Ok(())
}

Name(name) => match scope.resolve_name(name, expr.span)? {
Some(NamedThing::SelfValue { .. }) => Ok(()),
Some(NamedThing::Item(_)) | None => Err(invalid_assign_target(scope, expr)),
Some(NamedThing::Variable { kind, .. }) => match kind {
NameBindingKind::LetMut => Ok(()),
Name(name) => match scope.resolve_name(name, target.span)? {
Some(NamedThing::SelfValue { .. })
| Some(NamedThing::Item(_))
| None => Err(invalid_assign_target(scope, target)),
Some(NamedThing::Variable { mutability, .. }) => match mutability {
BindingMutability::Mutable => Ok(()),

NameBindingKind::Let => {
BindingMutability::Immutable => {
scope.fancy_error(&format!("`{}` is not mutable", name), // XXX better error
vec![Label::primary(expr.span, "")],
vec![Label::primary(target.span, "")],
vec![]);
Ok(())
}
NameBindingKind::Const => {
BindingMutability::Const => {
Err(FatalError::new(scope.fancy_error("cannot assign to constant variable",
vec![Label::primary(expr.span, "")],
vec![Label::primary(target.span, "")],
vec!["The left side of an assignment can be a variable name, attribute, subscript, or tuple.".into()])))
}
}
},

_ => Err(invalid_assign_target(scope, expr)),
_ => Err(invalid_assign_target(scope, target)),
}
}

fn invalid_assign_target(scope: &mut BlockScope, expr: &Node<fe::Expr>) -> FatalError {
fn invalid_assign_target(scope: &mut BlockScope, target: &Node<fe::Expr>) -> FatalError {
FatalError::new(scope.fancy_error("invalid assignment target",
vec![Label::primary(expr.span, "")],
vec!["The left side of an assignment can be a variable name, attribute, subscript, or tuple.".into()]))
vec![Label::primary(target.span, "")],
vec!["The left side of an assignment can be a variable name, attribute, subscript, or tuple.".into()]))
}

/// Gather context information for assignments and check for type errors.
pub fn aug_assign(scope: &mut BlockScope, stmt: &Node<fe::FuncStmt>) -> Result<(), FatalError> {
if let fe::FuncStmt::AugAssign { target, op, value } = &stmt.kind {
check_assign_target(scope, target)?;
let target_attributes = expressions::expr(scope, target, None)?;
let value_attributes = expressions::expr(scope, value, Some(&target_attributes.typ))?;

if let Err(err) = operations::bin(&target_attributes.typ, &op.kind, &value_attributes.typ) {
add_bin_operations_errors(
scope,
&op.kind,
target.span,
&target_attributes.typ,
value.span,
&value_attributes.typ,
err,
);
}
return Ok(());
}
let (target, op, rhs) = match &stmt.kind {
fe::FuncStmt::AugAssign { target, op, value } => (target, op, value),
_ => unreachable!(),
};

let target_attributes = expressions::expr(scope, target, None)?;
let rhs_attributes = expressions::expr(scope, rhs, Some(&target_attributes.typ))?;
check_assign_target(scope, target)?;

unreachable!()
if let Err(err) = operations::bin(&target_attributes.typ, &op.kind, &rhs_attributes.typ) {
add_bin_operations_errors(
scope,
&op.kind,
target.span,
&target_attributes.typ,
rhs.span,
&rhs_attributes.typ,
err,
);
}
Ok(())
}
Loading

0 comments on commit 26cba7a

Please sign in to comment.