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

Structs: Fields check #1907

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
4 changes: 1 addition & 3 deletions ast/src/analyzed/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ impl<T: Display> Display for Analyzed<T> {
if matches!(
definition,
Some(FunctionValueDefinition::TypeConstructor(_, _))
) || matches!(
definition,
Some(FunctionValueDefinition::TraitFunction(_, _))
| Some(FunctionValueDefinition::TraitFunction(_, _))
) {
// These are printed as part of the enum / trait.
continue;
Expand Down
34 changes: 31 additions & 3 deletions pil-analyzer/src/expression_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use powdr_ast::{
parsed::{
self, asm::SymbolPath, types::Type, ArrayExpression, ArrayLiteral, BinaryOperation,
BlockExpression, IfExpression, LambdaExpression, LetStatementInsideBlock, MatchArm,
MatchExpression, NamespacedPolynomialReference, Number, Pattern, SelectedExpressions,
StatementInsideBlock, SymbolCategory, UnaryOperation,
MatchExpression, NamedExpression, NamespacedPolynomialReference, Number, Pattern,
SelectedExpressions, StatementInsideBlock, StructExpression, SymbolCategory,
UnaryOperation,
},
};

Expand Down Expand Up @@ -189,7 +190,34 @@ impl<'a, D: AnalysisDriver> ExpressionProcessor<'a, D> {
self.process_block_expression(statements, expr, src)
}
PExpression::FreeInput(_, _) => panic!(),
PExpression::StructExpression(_, _) => unimplemented!("Structs are not supported yet"),
PExpression::StructExpression(
src,
StructExpression {
name: reference,
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
fields,
},
) => {
let type_args = reference
.type_args
.map(|args| args.into_iter().map(|t| self.process_type(t)).collect());

Expression::StructExpression(
src,
StructExpression {
name: Reference::Poly(PolynomialReference {
name: self.driver.resolve_type_ref(&reference.path),
type_args,
}),
fields: fields
.into_iter()
.map(|named_expr| NamedExpression {
name: named_expr.name,
body: Box::new(self.process_expression(*named_expr.body)),
})
.collect(),
},
)
}
}
}

Expand Down
68 changes: 66 additions & 2 deletions pil-analyzer/src/pil_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use powdr_ast::parsed::asm::{
use powdr_ast::parsed::types::Type;
use powdr_ast::parsed::visitor::{AllChildren, Children};
use powdr_ast::parsed::{
self, FunctionKind, LambdaExpression, PILFile, PilStatement, SymbolCategory,
TraitImplementation,
self, FunctionKind, LambdaExpression, PILFile, PilStatement, StructDeclaration, SymbolCategory,
TraitImplementation, TypeDeclaration,
};
use powdr_number::{FieldElement, GoldilocksField};

Expand Down Expand Up @@ -56,6 +56,7 @@ fn analyze<T: FieldElement>(files: Vec<PILFile>) -> Result<Analyzed<T>, Vec<Erro
let mut analyzer = PILAnalyzer::new();
analyzer.process(files)?;
analyzer.side_effect_check()?;
analyzer.structs_check()?;
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
analyzer.type_check()?;
// TODO should use Result here as well.
let solved_impls = analyzer.resolve_trait_impls();
Expand Down Expand Up @@ -257,6 +258,69 @@ impl PILAnalyzer {
}
}

pub fn structs_check(&self) -> Result<(), Vec<Error>> {
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
let mut errors = vec![];
let struct_declarations: HashMap<String, StructDeclaration> = self
.definitions
.iter()
.filter_map(|(name, (_, value))| {
if let Some(FunctionValueDefinition::TypeDeclaration(TypeDeclaration::Struct(
struct_decl,
))) = value
{
Some((name.clone(), struct_decl.clone()))
} else {
None
}
})
.collect();

for (_, def) in self.definitions.values() {
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
let Some(def) = def else { continue };

def.all_children().for_each(|expr| {
if let Expression::StructExpression(sr, struct_expr) = expr {
let Some(struct_decl) = struct_declarations.get(&struct_expr.name.to_string())
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
else {
errors.push(sr.with_error(format!(
"Struct '{}' has not been declared",
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
struct_expr.name
)));
return;
};

let mut used_fields = HashSet::new();
gzanitti marked this conversation as resolved.
Show resolved Hide resolved

for named_expr in &struct_expr.fields {
if !struct_decl.fields.iter().any(|f| f.name == named_expr.name) {
errors.push(sr.with_error(format!(
"Field '{}' not found in struct '{}'",
named_expr.name, struct_expr.name
)));
} else {
used_fields.insert(named_expr.name.clone());
}
}

for field in &struct_decl.fields {
if !used_fields.contains(&field.name) {
errors.push(sr.with_error(format!(
"Field '{}' is declared but never used in struct '{}' initialization",
field.name, struct_expr.name
)));
}
}
}
});
}

if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}

pub fn type_check(&mut self) -> Result<(), Vec<Error>> {
let query_type: Type = parse_type("int -> std::prelude::Query").unwrap().into();
let mut expressions = vec![];
Expand Down
36 changes: 36 additions & 0 deletions pil-analyzer/tests/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,39 @@ fn prover_functions() {
";
type_check(input, &[("a", "", "int"), ("b", "", "()[]")]);
}

gzanitti marked this conversation as resolved.
Show resolved Hide resolved
#[test]
#[should_panic = "Type symbol not found: NotADot"]
fn wrong_struct() {
let input = "
struct Dot { x: int, y: int }
let f: int -> Dot = |i| NotADot{x: 0, y: i};
let x = f(0);
";
type_check(input, &[]);
}

#[test]
#[should_panic = "Field 'a' not found in struct 'Dot'"]
fn struct_wrong_fields() {
let input = "
struct Dot { x: int, y: int }
let f: int -> Dot = |i| Dot{a: 0, y: i};
let x = f(0);
";
type_check(input, &[]);
}

#[test]
#[should_panic = "Field 'z' is declared but never used in struct 'Point' initialization"]
gzanitti marked this conversation as resolved.
Show resolved Hide resolved
fn test_struct_unused_fields() {
let input = " struct Point {
x: int,
y: int,
z: int,
}
let p: Point = Point{ y: 0, x: 2 };
";

type_check(input, &[]);
}