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

Implement named constructor #188

Merged
merged 2 commits into from
Feb 23, 2024
Merged
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
8 changes: 6 additions & 2 deletions ante-ls/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,12 @@ fn walk_ast<'a>(ast: &'a Ast<'a>, idx: usize) -> &'a Ast<'a> {
}
},
Ast::NamedConstructor(n) => {
if let Some((_, arg)) = n.args.iter().find(|(_, arg)| arg.locate().contains_index(&idx)) {
ast = arg;
let statements = match n.sequence.as_ref() {
Ast::Sequence(s) => &s.statements,
_ => unreachable!(),
};
if let Some(stmt) = statements.iter().find(|stmt| stmt.locate().contains_index(&idx)) {
ast = stmt;
} else if n.constructor.locate().contains_index(&idx) {
ast = &n.constructor;
} else {
Expand Down
23 changes: 23 additions & 0 deletions examples/nameresolution/named_constructor.an
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type T a b =
x: a
y: b

x = 4

t1 = T with x = 3, z = 5
t2 = T with y = 3.2, x

// Declarations should not leak from the named constructor
z = y * 2.0

// args: --check
// expected stderr:
// named_constructor.an:7:6 error: Missing fields: y
// t1 = T with x = 3, z = 5
//
// named_constructor.an:7:20 error: z is not a struct field
// t1 = T with x = 3, z = 5
//
// named_constructor.an:11:5 error: No declaration for `y` was found in scope
// z = y * 2.0

16 changes: 16 additions & 0 deletions examples/typechecking/named_constructor.an
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type Foo a b=
x: a
y: b

x = "Hello World"

hello_foo y = Foo with x, y

foo = hello_foo 42

// args: --check --show-types
// expected stdout:
// Foo : (forall a b c. (a - b -> (Foo a b) can c))
// foo : (Foo String (Int a))
// hello_foo : (forall a b. (a -> (Foo String a) can b))
// x : String
17 changes: 16 additions & 1 deletion src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ pub enum DiagnosticKind {
HandlerMissingCases(/*missing effect cases*/ Vec<String>),
ImportShadowsPreviousDefinition(/*item name*/ String),
Unused(/*item name*/ String),
NotAStruct(/*struct name*/ String),
MissingFields(/*missing struct fields*/ Vec<String>),
NotAStructField(/*field name*/ String),

//
// Type Checking
Expand Down Expand Up @@ -206,6 +209,15 @@ impl Display for DiagnosticKind {
DiagnosticKind::Unused(item) => {
write!(f, "{item} is unused (prefix name with _ to silence this warning)")
},
DiagnosticKind::NotAStruct(name) => {
write!(f, "{} is not a struct", name)
},
DiagnosticKind::NotAStructField(name) => {
write!(f, "{} is not a struct field", name)
},
DiagnosticKind::MissingFields(fields) => {
write!(f, "Missing fields: {}", fields.join(", "))
},
DiagnosticKind::TypeLengthMismatch(left, right) => {
write!(
f,
Expand Down Expand Up @@ -366,7 +378,10 @@ impl DiagnosticKind {
| MultipleMatchingImpls(_, _)
| NoMatchingImpls(_)
| MissingCase(_)
| InternalError(_) => Error,
| InternalError(_)
| NotAStruct(_)
| MissingFields(_)
| NotAStructField(_) => Error,
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/hir/monomorphisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl<'c> Context<'c> {
Assignment(assignment) => self.monomorphise_assignment(assignment),
EffectDefinition(_) => todo!(),
Handle(_) => todo!(),
NamedConstructor(_) => todo!(),
NamedConstructor(constructor) => self.monomorphise_named_constructor(constructor),
}
}

Expand Down Expand Up @@ -1586,6 +1586,13 @@ impl<'c> Context<'c> {
hir::Ast::Assignment(hir::Assignment { lhs: Box::new(lhs), rhs: Box::new(self.monomorphise(&assignment.rhs)) })
}

fn monomorphise_named_constructor(&mut self, constructor: &ast::NamedConstructor<'c>) -> hir::Ast {
match constructor.sequence.as_ref() {
ast::Ast::Sequence(sequence) => self.monomorphise_sequence(sequence),
_ => unreachable!(),
}
}

pub fn extract(ast: hir::Ast, member_index: u32, result_type: Type) -> hir::Ast {
use hir::{
Ast,
Expand Down
2 changes: 1 addition & 1 deletion src/llvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn run(path: &Path, ast: hir::Ast, args: &Cli) {

// Run the program by default if --build was not passed
if !args.build {
let program_command = PathBuf::from("./".to_string() + &binary_name);
let program_command = PathBuf::from(&binary_name);
Command::new(program_command).spawn().unwrap().wait().unwrap();
}

Expand Down
99 changes: 97 additions & 2 deletions src/nameresolution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,102 @@ impl<'c> Resolvable<'c> for ast::Handle<'c> {
impl<'c> Resolvable<'c> for ast::NamedConstructor<'c> {
fn declare(&mut self, _resolver: &mut NameResolver, _cache: &mut ModuleCache<'c>) {}

fn define(&mut self, _resolver: &mut NameResolver, _cache: &mut ModuleCache<'c>) {
todo!()
fn define(&mut self, resolver: &mut NameResolver, cache: &mut ModuleCache<'c>) {
let type_name = match self.constructor.as_ref() {
Ast::Variable(ast::Variable { kind, .. }) => kind.name(),
_ => {
// This should never happen since constructor is parsed with the `variant` parser
cache.push_diagnostic(
self.constructor.locate(),
D::InternalError("Expected consturctor field to be a Variable"),
);
return;
},
};
// This will increment the use count for that type.
// It will result in it being one higher than it needs to,
// as the define pass on the sequence will do it again,
// since it ends with a FunctionCall. Is that a problem?
let type_info = match resolver.lookup_type(type_name.as_ref(), cache) {
Some(id) => &cache.type_infos[id.0],
None => {
cache.push_diagnostic(self.location, D::NotInScope("Type", type_name.as_ref().clone()));
return;
},
};

// Field names in the order they appear in the type definition
let struct_fields = match &type_info.body {
TypeInfoBody::Struct(fields) => fields.iter().map(|field| &field.name),
_ => {
cache.push_diagnostic(self.constructor.locate(), D::NotAStruct(type_name.as_ref().clone()));
return;
},
};
let statements = match self.sequence.as_mut() {
Ast::Sequence(ast::Sequence { statements, .. }) => statements,
_ => {
// This should never happen again, but it's better to emit an error than panic
cache.push_diagnostic(self.location, D::InternalError("Expected statements field to be a Variable"));
return;
},
};

// Fields referenced in the constructor
let mut defined_fields = statements
.iter()
.map(|stmt| {
let (variable, location) = match stmt {
Ast::Definition(ast::Definition { pattern, location, .. }) => (pattern.as_ref(), location),
Ast::Variable(v) => (stmt, &v.location),
_ => unreachable!(),
};

let name = match variable {
Ast::Variable(ast::Variable { kind: ast::VariableKind::Identifier(name), .. }) => name,
_ => unreachable!(),
};

(name, (variable, location))
})
.collect::<HashMap<_, _>>();

let (missing_fields, args) =
struct_fields.fold((Vec::new(), Vec::new()), |(mut missing_fields, mut args), field| {
if let Some((variable, _)) = defined_fields.remove(field) {
args.push(variable.clone());
} else {
missing_fields.push(field);
}
(missing_fields, args)
});

let has_missing_fields = !missing_fields.is_empty();
let has_unknown_fields = !defined_fields.is_empty();

if has_missing_fields {
cache.push_diagnostic(
self.constructor.locate(),
D::MissingFields(missing_fields.into_iter().cloned().collect()),
);
}

for (name, (_, location)) in defined_fields {
cache.push_diagnostic(*location, D::NotAStructField(name.clone()));
}

if has_missing_fields || has_unknown_fields {
return;
}

let call = ast::Ast::function_call(self.constructor.as_ref().clone(), args, self.location);

// We only want to keep definitions in the sequence to keep the Hir simpler
statements.retain(|stmt| matches!(stmt, Ast::Definition(_)));
statements.push(call);

resolver.push_scope(cache);
self.sequence.define(resolver, cache);
resolver.pop_scope(cache, false, None);
}
}
12 changes: 8 additions & 4 deletions src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,7 @@ pub struct Handle<'a> {
#[derive(Debug, Clone)]
pub struct NamedConstructor<'a> {
pub constructor: Box<Ast<'a>>,
pub args: Vec<(String, Ast<'a>)>,

pub sequence: Box<Ast<'a>>,
pub location: Location<'a>,
pub typ: Option<types::Type>,
}
Expand Down Expand Up @@ -709,8 +708,13 @@ impl<'a> Ast<'a> {
Ast::Handle(Handle { expression: Box::new(expression), branches, location, resumes: vec![], typ: None })
}

pub fn named_constructor(constructor: Ast<'a>, args: Vec<(String, Ast<'a>)>, location: Location<'a>) -> Ast<'a> {
Ast::NamedConstructor(NamedConstructor { constructor: Box::new(constructor), args, location, typ: None })
pub fn named_constructor(constructor: Ast<'a>, sequence: Ast<'a>, location: Location<'a>) -> Ast<'a> {
Ast::NamedConstructor(NamedConstructor {
constructor: Box::new(constructor),
sequence: Box::new(sequence),
location,
typ: None,
})
}

/// This is a bit of a hack.
Expand Down
29 changes: 16 additions & 13 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,36 +458,39 @@ parser!(function_call loc =
parser!(named_constructor_expr loc =
constructor <- variant;
_ <- expect(Token::With);
args !<- named_constructor_args;
Ast::named_constructor(constructor, args, loc)
sequence !<- named_constructor_args;
Ast::named_constructor(constructor, sequence, loc)
);

fn named_constructor_args<'a, 'b>(input: Input<'a, 'b>) -> ParseResult<'a, 'b, Vec<(String, Ast<'b>)>> {
fn named_constructor_args<'a, 'b>(input: Input<'a, 'b>) -> ParseResult<'a, 'b, Ast<'b>> {
if let Token::Indent = input[0].0 {
named_constructor_block_args(input)
} else {
named_constructor_inline_args(input)
}
}

parser!(named_constructor_block_args loc -> 'b Vec<(String, Ast<'b>)> =
parser!(named_constructor_block_args loc -> 'b Ast<'b> =
_ <- expect(Token::Indent);
args <- delimited_trailing(named_constructor_arg, expect(Token::Newline), false);
statements <- delimited_trailing(named_constructor_arg, expect(Token::Newline), false);
_ !<- expect(Token::Unindent);
args
Ast::sequence(statements, loc)
);

parser!(named_constructor_inline_args loc -> 'b Vec<(String, Ast<'b>)> =
args <- delimited(named_constructor_arg, expect(Token::Comma));
args
parser!(named_constructor_inline_args loc -> 'b Ast<'b> =
statements <- delimited(named_constructor_arg, expect(Token::Comma));
Ast::sequence(statements, loc)
);

fn named_constructor_arg<'a, 'b>(input: Input<'a, 'b>) -> ParseResult<'a, 'b, (String, Ast<'b>)> {
fn named_constructor_arg<'a, 'b>(input: Input<'a, 'b>) -> ParseResult<'a, 'b, Ast<'b>> {
let (input, ident, start) = identifier(input)?;
let field_name = Ast::variable(vec![], ident, start);
let (input, maybe_expr, end) = maybe(pair(expect(Token::Equal), function_argument))(input)?;
// Desugar bar, baz into bar = bar, baz = baz
let expr = maybe_expr.map_or_else(|| Ast::variable(vec![], ident.clone(), start), |(_, expr)| expr);
Ok((input, (ident, expr), start.union(end)))
let expr = match maybe_expr {
Some((_, expr)) => Ast::definition(field_name, expr, start.union(end)),
None => field_name,
};
Ok((input, expr, start.union(end)))
}

parser!(pattern_function_call loc =
Expand Down
11 changes: 10 additions & 1 deletion src/parser/pretty_printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,16 @@ impl<'a> Display for ast::Handle<'a> {

impl<'a> Display for ast::NamedConstructor<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let args = fmap(self.args.iter(), |(name, expr)| format!("{name} = {expr}"));
let statements = match self.sequence.as_ref() {
Ast::Sequence(ast::Sequence { statements, .. }) => statements,
_ => unreachable!(),
};
let args = fmap(statements, |stmt| match stmt {
Ast::Definition(ast::Definition { pattern, expr, .. }) => format!("{pattern} = {expr}"),
Ast::Variable(v) => format!("{v} = {v}"),
_ => unreachable!(),
});

write!(f, "({} with {})", self.constructor, args.join(", "))
}
}
4 changes: 2 additions & 2 deletions src/types/typechecker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2060,7 +2060,7 @@ impl<'a> Inferable<'a> for ast::Handle<'a> {
}

impl<'a> Inferable<'a> for ast::NamedConstructor<'a> {
fn infer_impl(&mut self, _checker: &mut ModuleCache<'a>) -> TypeResult {
todo!()
fn infer_impl(&mut self, cache: &mut ModuleCache<'a>) -> TypeResult {
self.sequence.infer_impl(cache)
}
}
Loading