Structs: Fields check (#1907)

Note: Like #1910, this PR also includes the same modifications to
expression processing, in order to run tests.
This commit is contained in:
Gastón Zanitti
2024-10-25 11:48:12 -03:00
committed by GitHub
parent 3617c90c14
commit c40f28e9e9
7 changed files with 264 additions and 12 deletions

View File

@@ -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;

View File

@@ -40,19 +40,25 @@ pub enum SymbolCategory {
/// A type constructor, i.e. an enum variant, which can be used as a function or constant inside an expression
/// or to deconstruct a value in a pattern.
TypeConstructor,
/// A trait declaration, which can be used as a type.
/// A trait declaration
TraitDeclaration,
/// A struct, which can be used as a type.
Struct,
}
impl SymbolCategory {
/// Returns if a symbol of a given category can satisfy a request for a certain category.
pub fn compatible_with_request(&self, request: SymbolCategory) -> bool {
match self {
SymbolCategory::Value => request == SymbolCategory::Value,
SymbolCategory::Type => request == SymbolCategory::Type,
SymbolCategory::Struct => {
// Structs can also satisfy requests for types.
request == SymbolCategory::Struct || request == SymbolCategory::Type
}
SymbolCategory::TypeConstructor => {
// Type constructors can also satisfy requests for values.
request == SymbolCategory::TypeConstructor || request == SymbolCategory::Value
}
SymbolCategory::Value => request == SymbolCategory::Value,
SymbolCategory::Type => request == SymbolCategory::Type,
SymbolCategory::TraitDeclaration => request == SymbolCategory::TraitDeclaration,
}
}
@@ -153,7 +159,7 @@ impl PilStatement {
),
),
PilStatement::StructDeclaration(_, StructDeclaration { name, .. }) => {
Box::new(once((name, None, SymbolCategory::Type)))
Box::new(once((name, None, SymbolCategory::Struct)))
}
PilStatement::TraitDeclaration(
_,

View File

@@ -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,
},
};
@@ -189,7 +190,28 @@ 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, fields }) => {
let type_args = name
.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_ref(&name.path, SymbolCategory::Struct),
type_args,
}),
fields: fields
.into_iter()
.map(|named_expr| NamedExpression {
name: named_expr.name,
body: Box::new(self.process_expression(*named_expr.body)),
})
.collect(),
},
)
}
}
}

View File

@@ -7,6 +7,7 @@ pub mod expression_processor;
mod pil_analyzer;
mod side_effect_checker;
mod statement_processor;
mod structural_checks;
mod traits_resolver;
mod type_builtins;
mod type_inference;

View File

@@ -6,6 +6,7 @@ use std::iter::once;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::structural_checks::check_structs_fields;
use itertools::Itertools;
use powdr_ast::parsed::asm::{
parse_absolute_path, AbsoluteSymbolPath, ModuleStatement, SymbolPath,
@@ -14,14 +15,13 @@ use powdr_ast::parsed::types::Type;
use powdr_ast::parsed::visitor::{AllChildren, Children};
use powdr_ast::parsed::{
self, FunctionKind, LambdaExpression, PILFile, PilStatement, SymbolCategory,
TraitImplementation,
TraitImplementation, TypedExpression,
};
use powdr_number::{FieldElement, GoldilocksField};
use powdr_ast::analyzed::{
type_from_definition, Analyzed, DegreeRange, Expression, FunctionValueDefinition,
PolynomialType, PublicDeclaration, Reference, StatementIdentifier, Symbol, SymbolKind,
TypedExpression,
};
use powdr_parser::{parse, parse_module, parse_type};
use powdr_parser_util::Error;
@@ -52,6 +52,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.validate_structs()?;
analyzer.type_check()?;
let solved_impls = analyzer.resolve_trait_impls()?;
analyzer.condense(solved_impls)
@@ -253,6 +254,14 @@ impl PILAnalyzer {
}
}
pub fn validate_structs(&self) -> Result<(), Vec<Error>> {
let structs_exprs = self
.all_children()
.filter(|expr| matches!(expr, Expression::StructExpression(_, _)));
check_structs_fields(structs_exprs, &self.definitions)
}
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![];
@@ -538,6 +547,40 @@ impl PILAnalyzer {
}
}
impl Children<Expression> for PILAnalyzer {
fn children(&self) -> Box<dyn Iterator<Item = &Expression> + '_> {
Box::new(
self.definitions
.values()
.filter_map(|(_, def)| def.as_ref())
.flat_map(|def| def.children())
.chain(
self.trait_impls
.values()
.flat_map(|impls| impls.iter())
.flat_map(|impl_| impl_.children()),
)
.chain(self.proof_items.iter()),
)
}
fn children_mut(&mut self) -> Box<dyn Iterator<Item = &mut Expression> + '_> {
Box::new(
self.definitions
.values_mut()
.filter_map(|(_, def)| def.as_mut())
.flat_map(|def| def.children_mut())
.chain(
self.trait_impls
.values_mut()
.flat_map(|impls| impls.iter_mut())
.flat_map(|impl_| impl_.children_mut()),
)
.chain(self.proof_items.iter_mut()),
)
}
}
#[derive(Clone, Copy)]
struct Driver<'a>(&'a PILAnalyzer);

View File

@@ -0,0 +1,110 @@
use itertools::Itertools;
use std::collections::{HashMap, HashSet};
use powdr_ast::{
analyzed::{Expression, FunctionValueDefinition, PolynomialReference, Reference, Symbol},
parsed::{StructExpression, TypeDeclaration},
};
use powdr_parser_util::{Error, SourceRef};
/// Verifies that all struct instantiations match their corresponding declarations
/// (existence of field names, completeness) and ensures that both are correct.
pub fn check_structs_fields<'a>(
structs_exprs: impl Iterator<Item = &'a Expression>,
definitions: &HashMap<String, (Symbol, Option<FunctionValueDefinition>)>,
) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
for expr in structs_exprs {
let Expression::StructExpression(
source,
StructExpression {
name: Reference::Poly(PolynomialReference { name, .. }),
fields,
},
) = expr
else {
unreachable!()
};
errors.extend(check_struct_expression(definitions, name, fields, source));
}
errors.extend(check_struct_declarations(definitions));
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn check_struct_expression(
definitions: &HashMap<String, (Symbol, Option<FunctionValueDefinition>)>,
name: &String,
fields: &[powdr_ast::parsed::NamedExpression<Box<powdr_ast::parsed::Expression<Reference>>>],
source: &SourceRef,
) -> Vec<Error> {
let mut errors = Vec::new();
if let (
_,
Some(FunctionValueDefinition::TypeDeclaration(TypeDeclaration::Struct(struct_decl))),
) = definitions.get(name).unwrap()
{
let declared_fields: HashSet<_> = struct_decl.fields.iter().map(|f| &f.name).collect();
let used_fields: HashSet<_> = fields.iter().map(|f| &f.name).collect();
errors.extend(
fields
.iter()
.filter(|f| !declared_fields.contains(&f.name))
.map(|f| {
source.with_error(format!("Struct '{name}' has no field named '{}'", f.name))
}),
);
errors.extend(declared_fields.difference(&used_fields).map(|&f| {
source.with_error(format!("Missing field '{f}' in initializer of '{name}'",))
}));
let duplicate_fields: Vec<_> = fields.iter().map(|f| &f.name).duplicates().collect();
errors.extend(
duplicate_fields
.into_iter()
.map(|f| source.with_error(format!("Field '{f}' specified more than once"))),
);
} else {
panic!("Struct '{name}' has not been declared");
}
errors
}
fn check_struct_declarations(
definitions: &HashMap<String, (Symbol, Option<FunctionValueDefinition>)>,
) -> Vec<Error> {
let mut errors = Vec::new();
for (symbol, def) in definitions.values() {
let Some(FunctionValueDefinition::TypeDeclaration(TypeDeclaration::Struct(struct_decl))) =
def
else {
continue;
};
let duplicate_declaration_fields: Vec<_> = struct_decl
.fields
.iter()
.map(|f| &f.name)
.duplicates()
.collect();
errors.extend(duplicate_declaration_fields.into_iter().map(|f| {
symbol
.source
.with_error(format!("Field '{f}' is declared more than once"))
}));
}
errors
}

View File

@@ -762,6 +762,78 @@ fn prover_functions() {
type_check(input, &[("a", "", "int"), ("b", "", "()[]")]);
}
#[test]
#[should_panic = "Struct 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};
";
type_check(input, &[]);
}
#[test]
#[should_panic = "Struct 'Dot' has no field named 'a'"]
fn struct_wrong_fields() {
let input = "
struct Dot { x: int, y: int }
let f: int -> Dot = |i| Dot{x: 0, y: i, a: 2};
let x = f(0);
";
type_check(input, &[]);
}
#[test]
#[should_panic = "Missing field 'z' in initializer of 'A'"]
fn test_struct_unused_fields() {
let input = " struct A {
x: int,
y: int,
z: int,
}
let x = A{ y: 0, x: 2 };
";
type_check(input, &[]);
}
#[test]
#[should_panic = "Field 'y' specified more than once"]
fn test_struct_repeated_fields_expr() {
let input = " struct A {
x: int,
y: int,
}
let x = A{ y: 0, x: 2, y: 1 };
";
type_check(input, &[]);
}
#[test]
#[should_panic = "Field 'x' is declared more than once"]
fn test_struct_repeated_fields_decl() {
let input = " struct A {
x: int,
y: int,
x: int,
}
let x = A{ y: 0, x: 2 };
";
type_check(input, &[]);
}
#[test]
#[should_panic(expected = "Expected symbol of kind Struct but got Type: A")]
fn enum_used_as_struct() {
let input = "
enum A { X }
let a = A{x: 8};
";
type_check(input, &[]);
}
#[test]
fn typed_literals() {
let input = "