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

Add pub attribute for struct fields #232

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion examples/assignment.no
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
struct Thing {
xx: Field,
pub xx: Field,
}

fn try_to_mutate(thing: Thing) {
Expand Down
4 changes: 2 additions & 2 deletions examples/hint.no
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Thing {
xx: Field,
yy: Field,
pub xx: Field,
pub yy: Field,
}

hint fn mul(lhs: Field, rhs: Field) -> Field {
Expand Down
4 changes: 2 additions & 2 deletions examples/types.no
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Thing {
xx: Field,
yy: Field,
pub xx: Field,
pub yy: Field,
}

fn main(pub xx: Field, pub yy: Field) {
Expand Down
4 changes: 2 additions & 2 deletions examples/types_array.no
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Thing {
xx: Field,
yy: Field,
pub xx: Field,
pub yy: Field,
}

fn main(pub xx: Field, pub yy: Field) {
Expand Down
4 changes: 2 additions & 2 deletions examples/types_array_output.no
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct Thing {
xx: Field,
yy: Field,
pub xx: Field,
pub yy: Field,
}

fn main(pub xx: Field, pub yy: Field) -> [Thing; 2] {
Expand Down
2 changes: 1 addition & 1 deletion src/circuit_writer/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ impl<B: Backend> IRWriter<B> {
// find range of field
let mut start = 0;
let mut len = 0;
for (field, field_typ) in &struct_info.fields {
for (field, field_typ, _attribute) in &struct_info.fields {
if field == &rhs.value {
len = self.size_of(field_typ);
break;
Expand Down
4 changes: 2 additions & 2 deletions src/circuit_writer/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl<B: Backend> CircuitWriter<B> {
.clone();

let mut offset = 0;
for (_field_name, field_typ) in &struct_info.fields {
for (_field_name, field_typ, _attribute) in &struct_info.fields {
let len = self.size_of(field_typ);
let range = offset..(offset + len);
self.constrain_inputs_to_main(&input[range], field_typ, span)?;
Expand Down Expand Up @@ -491,7 +491,7 @@ impl<B: Backend> CircuitWriter<B> {
// find range of field
let mut start = 0;
let mut len = 0;
for (field, field_typ) in &struct_info.fields {
for (field, field_typ, _attribute) in &struct_info.fields {
if field == &rhs.value {
len = self.size_of(field_typ);
break;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,7 @@ pub enum ErrorKind {

#[error("division by zero")]
DivisionByZero,

#[error("cannot access private field `{1}` of struct `{0}` from outside its methods.")]
PrivateFieldAccess(String, String),
}
2 changes: 1 addition & 1 deletion src/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl<B: Backend> CompiledCircuit<B> {

// parse each field
let mut res = vec![];
for (field_name, field_ty) in fields {
for (field_name, field_ty, _attribute) in fields {
let value = map.remove(field_name).ok_or_else(|| {
ParsingError::MissingStructFieldIdent(field_name.to_string())
})?;
Expand Down
6 changes: 3 additions & 3 deletions src/mast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ impl<B: Backend> Mast<B> {

let mut sum = 0;

for (_, t) in &struct_info.fields {
for (_, t, _) in &struct_info.fields {
sum += self.size_of(t);
}

Expand Down Expand Up @@ -548,8 +548,8 @@ fn monomorphize_expr<B: Backend>(
let typ = struct_info
.fields
.iter()
.find(|(name, _)| name == &rhs.value)
.map(|(_, typ)| typ.clone());
.find(|(name, _, _)| name == &rhs.value)
.map(|(_, typ, _)| typ.clone());

let mexpr = expr.to_mast(
ctx,
Expand Down
2 changes: 1 addition & 1 deletion src/name_resolution/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl NameResCtx {
self.resolve(module, true)?;

// we resolve the fully-qualified types of the fields
for (_field_name, field_typ) in fields {
for (_field_name, field_typ, _attribute) in fields {
self.resolve_typ_kind(&mut field_typ.kind)?;
}

Expand Down
31 changes: 29 additions & 2 deletions src/negative_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ fn test_nonhint_call_with_unsafe() {
fn test_no_cst_struct_field_prop() {
let code = r#"
struct Thing {
val: Field,
pub val: Field,
}

fn gen(const LEN: Field) -> [Field; LEN] {
Expand All @@ -725,7 +725,7 @@ fn test_no_cst_struct_field_prop() {
fn test_mut_cst_struct_field_prop() {
let code = r#"
struct Thing {
val: Field,
pub val: Field,
}

fn gen(const LEN: Field) -> [Field; LEN] {
Expand All @@ -747,3 +747,30 @@ fn test_mut_cst_struct_field_prop() {
ErrorKind::ArgumentTypeMismatch(..)
));
}

#[test]
fn test_private_field_access() {
let code = r#"
struct Room {
pub beds: Field, // public
size: Field // private
}

fn Room.access_size(self) {
let room2 = Room {beds: self.beds, size: self.size};
}

fn main(pub beds: Field) {
let mut room = Room {beds: 2, size: 10};
room.beds = beds; // allowed
room.size = 5; // not allowed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just test the negative case here. Then the positive tests should cover the rest.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree ^ we can simplify the test

room.access_size(); // allowed
}
"#;

let res = tast_pass(code).0;
assert!(matches!(
res.unwrap_err().kind,
ErrorKind::PrivateFieldAccess(..)
));
}
28 changes: 24 additions & 4 deletions src/parser/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use serde::{Deserialize, Serialize};
use crate::{
constants::Span,
error::{ErrorKind, Result},
lexer::{Token, TokenKind, Tokens},
lexer::{Keyword, Token, TokenKind, Tokens},
syntax::is_type,
};

use super::{
types::{Ident, ModulePath, Ty, TyKind},
types::{Attribute, AttributeKind, Ident, ModulePath, Ty, TyKind},
Error, ParserCtx,
};

Expand All @@ -17,7 +17,7 @@ pub struct StructDef {
//pub attribute: Attribute,
pub module: ModulePath, // name resolution
pub name: CustomType,
pub fields: Vec<(Ident, Ty)>,
pub fields: Vec<(Ident, Ty, Option<Attribute>)>,
pub span: Span,
}

Expand Down Expand Up @@ -55,6 +55,26 @@ impl StructDef {
tokens.bump(ctx);
break;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

realized looking at this code that we can write an empty struct (e.g. struct Thing {}) and it will work. Should we disallow that? (we could do that by checking that the length of fields is not 0 at the end)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the same time it seems OK to allow it...

// check for pub keyword
// struct Foo { pub a: Field, b: Field }
// ^
let attribute = if matches!(
tokens.peek(),
Some(Token {
kind: TokenKind::Keyword(Keyword::Pub),
..
})
) {
tokens.bump(ctx);
Some(Attribute {
kind: AttributeKind::Pub,
span: ctx.last_span(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be the span from the matching token

})
} else {
None
};

// struct Foo { a: Field, b: Field }
// ^
let field_name = Ident::parse(ctx, tokens)?;
Expand All @@ -67,7 +87,7 @@ impl StructDef {
// ^^^^^
let field_ty = Ty::parse(ctx, tokens)?;
span = span.merge_with(field_ty.span);
fields.push((field_name, field_ty));
fields.push((field_name, field_ty, attribute));

// struct Foo { a: Field, b: Field }
// ^ ^
Expand Down
2 changes: 1 addition & 1 deletion src/stdlib/native/int/lib.no
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ hint fn divmod(dividend: Field, divisor: Field) -> [Field; 2];
// u8
// must use new() to create a Uint8, so the value is range checked
struct Uint8 {
inner: Field,
pub inner: Field,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo we shouldn't expose it like this, we can have a to_field function but we shouldn't allow direct mutation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, having this as private was failing the tests here:

return res.inner;

I changed it to to_field() instead now.

}

fn Uint8.new(val: Field) -> Uint8 {
Expand Down
2 changes: 1 addition & 1 deletion src/tests/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use mimoo::liblib;

// test a library's type that links to its own type
struct Inner {
inner: Field,
pub inner: Field,
}

struct Lib {
Expand Down
40 changes: 33 additions & 7 deletions src/type_checker/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use crate::{
imports::FnKind,
parser::{
types::{
is_numeric, FnSig, ForLoopArgument, FunctionDef, Stmt, StmtKind, Symbolic, Ty, TyKind,
is_numeric, Attribute, AttributeKind, FnSig, ForLoopArgument, FuncOrMethod,
FunctionDef, Stmt, StmtKind, Symbolic, Ty, TyKind,
},
CustomType, Expr, ExprKind, Op2,
},
Expand Down Expand Up @@ -55,7 +56,7 @@ impl<B: Backend> FnInfo<B> {
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
pub struct StructInfo {
pub name: String,
pub fields: Vec<(String, TyKind)>,
pub fields: Vec<(String, TyKind, Option<Attribute>)>,
pub methods: HashMap<String, FunctionDef>,
}

Expand Down Expand Up @@ -116,14 +117,39 @@ impl<B: Backend> TypeChecker<B> {
.expect("this struct is not defined, or you're trying to access a field of a struct defined in a third-party library (TODO: better error)");

// find field type
let res = struct_info
if let Some((_, field_typ, attribute)) = struct_info
.fields
.iter()
.find(|(name, _)| name == &rhs.value)
.map(|(_, typ)| typ.clone());
.find(|(field_name, _, _)| field_name == &rhs.value)
{
// check for the pub attribute
let is_public = attribute
.as_ref()
.map(|attr| matches!(attr.kind, AttributeKind::Pub))
.unwrap_or(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you just do let is_public = matches!(attribute, &Some(Attribute { kind: AttributeKind::Pub, .. })) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(btw to avoid the option we could also have a AttributeKind::None)


// check if we're inside a method of the same struct
let in_method = if let Some(fn_kind) = typed_fn_env.current_fn_kind() {
match fn_kind {
FuncOrMethod::Method(method_struct) => {
method_struct.name == struct_name
}
FuncOrMethod::Function(_) => false,
}
} else {
false
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's gotta be a way to decrease the LOC here as well. Maybe

let in_method = matches!(typed_fn_env.fn_kind, FuncOrMethod::Method(struc) if struc.name == struct_name);


if let Some(res) = res {
Some(ExprTyInfo::new(lhs_node.var_name, res))
if is_public || in_method {
// allow access
Some(ExprTyInfo::new(lhs_node.var_name, field_typ.clone()))
} else {
// block access
Err(self.error(
ErrorKind::PrivateFieldAccess(struct_name.clone(), rhs.value.clone()),
expr.span,
))?
}
} else {
return Err(self.error(
ErrorKind::UndefinedField(struct_info.name.clone(), rhs.value.clone()),
Expand Down
13 changes: 12 additions & 1 deletion src/type_checker/fn_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::collections::HashMap;
use crate::{
constants::Span,
error::{Error, ErrorKind, Result},
parser::types::TyKind,
parser::types::{FuncOrMethod, TyKind},
};

/// Some type information on local variables that we want to track in the [TypedFnEnv] environment.
Expand Down Expand Up @@ -55,6 +55,9 @@ pub struct TypedFnEnv {

/// Determines if forloop variables are allowed to be accessed.
forbid_forloop_scope: bool,

/// The kind of function we're currently type checking
current_fn_kind: Option<FuncOrMethod>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it an option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, I thought we have global scope in the language and we might not be in a function while type checking. Should be fixed now.

}

impl TypedFnEnv {
Expand Down Expand Up @@ -182,4 +185,12 @@ impl TypedFnEnv {
Ok(None)
}
}

pub fn current_fn_kind(&self) -> Option<&FuncOrMethod> {
self.current_fn_kind.as_ref()
}

pub fn set_current_fn_kind(&mut self, kind: FuncOrMethod) {
self.current_fn_kind = Some(kind);
}
}
6 changes: 4 additions & 2 deletions src/type_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ impl<B: Backend> TypeChecker<B> {
let fields: Vec<_> = fields
.iter()
.map(|field| {
let (name, typ) = field;
(name.value.clone(), typ.kind.clone())
let (name, typ, attribute) = field;
(name.value.clone(), typ.kind.clone(), attribute.clone())
})
.collect();

Expand Down Expand Up @@ -331,6 +331,8 @@ impl<B: Backend> TypeChecker<B> {
// create a new typed fn environment to type check the function
let mut typed_fn_env = TypedFnEnv::default();

typed_fn_env.set_current_fn_kind(function.sig.kind.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you just do let mut typed_fn_env = TypeFnEnv::new(&function.sig.kind);


// if we're expecting a library, this should not be the main function
let is_main = function.is_main();
if is_main && is_lib {
Expand Down
Loading