Add behavioral types system

Implement behavioral properties for functions including:
- Property annotations: is pure, is total, is idempotent, is deterministic, is commutative
- Where clause constraints: where F is pure
- Result refinements: where result >= 0 (parsing only, not enforced)

Key changes:
- AST: BehavioralProperty enum, WhereClause enum, updated FunctionDecl
- Lexer: Added keywords (is, pure, total, idempotent, deterministic, commutative, where, assume)
- Parser: parse_behavioral_properties(), parse_where_clauses(), parse_single_property()
- Types: PropertySet for tracking function properties, updated Function type
- Typechecker: Verify pure functions don't have effects, validate where clause type params

Properties are informational/guarantees rather than type constraints - a pure
function can be used anywhere a function is expected. Property requirements
are meant to be enforced via where clauses (future work: call-site checking).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 03:30:51 -05:00
parent 15e5ccb064
commit 66132779cc
6 changed files with 624 additions and 4 deletions

View File

@@ -9,8 +9,8 @@ use crate::ast::{
};
use crate::modules::ModuleLoader;
use crate::types::{
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, Type, TypeEnv, TypeScheme,
VariantDef, VariantFieldsDef,
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, PropertySet, Type, TypeEnv,
TypeScheme, VariantDef, VariantFieldsDef,
};
/// Type checking error
@@ -234,6 +234,55 @@ impl TypeChecker {
span: func.span,
});
}
// Check behavioral properties
let properties = PropertySet::from_ast(&func.properties);
// Pure functions cannot have effects
if properties.is_pure() && !func.effects.is_empty() {
self.errors.push(TypeError {
message: format!(
"Function '{}' is declared as pure but has effects: {{{}}}",
func.name.name,
func.effects
.iter()
.map(|e| e.name.as_str())
.collect::<Vec<_>>()
.join(", ")
),
span: func.span,
});
}
// Check where clause property constraints
for where_clause in &func.where_clauses {
match where_clause {
ast::WhereClause::PropertyConstraint {
type_param,
property,
span,
} => {
// Record the constraint for later checking when the function is called
// For now, we just validate that the type parameter exists
if !func.type_params.iter().any(|p| p.name == type_param.name)
&& !func.params.iter().any(|p| p.name.name == type_param.name)
{
self.errors.push(TypeError {
message: format!(
"Unknown type parameter '{}' in where clause",
type_param.name
),
span: *span,
});
}
}
ast::WhereClause::ResultRefinement { predicate, span } => {
// Result refinements are checked at runtime or via SMT solver
// For now, we just type-check the predicate expression
// (would need 'result' in scope, which we don't have yet)
}
}
}
}
fn check_let_decl(&mut self, let_decl: &LetDecl) {
@@ -1089,8 +1138,9 @@ impl TypeChecker {
let return_type = self.resolve_type(&func.return_type);
let effects = EffectSet::from_iter(func.effects.iter().map(|e| e.name.clone()));
let properties = PropertySet::from_ast(&func.properties);
Type::function_with_effects(param_types, return_type, effects)
Type::function_with_properties(param_types, return_type, effects, properties)
}
fn effect_def(&self, effect: &EffectDecl) -> EffectDef {