From e0d5e6cc289d7d96d3a4271977bd186d58d04281 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 4 Jul 2024 19:29:52 -0400 Subject: [PATCH] Improve optional types --- crates/rue-compiler/src/compiler.rs | 19 ++++--- crates/rue-compiler/src/compiler/expr.rs | 2 + .../src/compiler/expr/binary_expr.rs | 20 +++---- .../src/compiler/expr/exists_expr.rs | 52 ++++++++++++++----- .../src/compiler/expr/field_access_expr.rs | 14 ++--- .../src/compiler/expr/guard_expr.rs | 45 ++++++++++++---- .../src/compiler/expr/initializer_expr.rs | 8 ++- .../src/compiler/expr/path_expr.rs | 22 ++++---- .../src/compiler/item/function_item.rs | 2 +- .../rue-compiler/src/compiler/stmt/if_stmt.rs | 9 +++- crates/rue-compiler/src/compiler/ty.rs | 4 +- .../ty/{optional_type.rs => nullable_type.rs} | 10 ++-- crates/rue-compiler/src/database.rs | 1 - .../rue-compiler/src/database/type_system.rs | 37 +++++++------ crates/rue-compiler/src/dependency_graph.rs | 2 +- crates/rue-compiler/src/error.rs | 8 +-- crates/rue-compiler/src/hir.rs | 1 - crates/rue-compiler/src/optimizer.rs | 24 --------- crates/rue-compiler/src/value.rs | 4 +- crates/rue-compiler/src/value/guard.rs | 28 ++++++++-- crates/rue-compiler/src/value/ty.rs | 2 +- crates/rue-parser/src/ast.rs | 16 ++++-- crates/rue-parser/src/grammar.rs | 6 ++- crates/rue-parser/src/syntax_kind.rs | 6 ++- tests/struct/struct_optional.rue | 2 +- 25 files changed, 214 insertions(+), 130 deletions(-) rename crates/rue-compiler/src/compiler/ty/{optional_type.rs => nullable_type.rs} (57%) diff --git a/crates/rue-compiler/src/compiler.rs b/crates/rue-compiler/src/compiler.rs index 618401a..bbe7946 100644 --- a/crates/rue-compiler/src/compiler.rs +++ b/crates/rue-compiler/src/compiler.rs @@ -11,7 +11,7 @@ use crate::{ database::{Database, HirId, ScopeId, SymbolId, TypeId}, hir::{Hir, Op}, scope::Scope, - value::{GuardPath, PairType, Type, Value}, + value::{GuardPath, Mutation, PairType, Type, TypeOverride, Value}, ErrorKind, }; @@ -45,7 +45,7 @@ pub struct Compiler<'a> { type_definition_stack: Vec, // The type guard stack is used for overriding types in certain contexts. - type_guard_stack: Vec>, + type_guard_stack: Vec>, // The generic type stack is used for overriding generic types that are being checked against. generic_type_stack: Vec>, @@ -210,13 +210,13 @@ impl<'a> Compiler<'a> { format!("fun({}) -> {}", params.join(", "), ret) } Type::Alias(..) => unreachable!(), - Type::Optional(ty) => { + Type::Nullable(ty) => { let inner = self.type_name_visitor(*ty, stack); format!("{inner}?") } - Type::PossiblyUndefined(ty) => { + Type::Optional(ty) => { let inner = self.type_name_visitor(*ty, stack); - format!("possibly undefined {inner}") + format!("optional {inner}") } }; @@ -261,7 +261,7 @@ impl<'a> Compiler<'a> { Value::new(self.builtins.unknown_hir, self.builtins.unknown) } - fn symbol_type(&self, guard_path: &GuardPath) -> Option { + fn symbol_type(&self, guard_path: &GuardPath) -> Option { for guards in &self.type_guard_stack { if let Some(guard) = guards.get(guard_path) { return Some(*guard); @@ -270,6 +270,13 @@ impl<'a> Compiler<'a> { None } + fn apply_mutation(&mut self, hir_id: HirId, mutation: Mutation) -> HirId { + match mutation { + Mutation::None => hir_id, + Mutation::UnwrapOptional => self.db.alloc_hir(Hir::Op(Op::First, hir_id)), + } + } + fn scope(&self) -> &Scope { self.db .scope(self.scope_stack.last().copied().expect("no scope found")) diff --git a/crates/rue-compiler/src/compiler/expr.rs b/crates/rue-compiler/src/compiler/expr.rs index aacb7fd..7ab0bd7 100644 --- a/crates/rue-compiler/src/compiler/expr.rs +++ b/crates/rue-compiler/src/compiler/expr.rs @@ -7,6 +7,7 @@ use super::Compiler; mod binary_expr; mod block_expr; mod cast_expr; +mod exists_expr; mod field_access_expr; mod function_call_expr; mod group_expr; @@ -47,6 +48,7 @@ impl Compiler<'_> { Expr::FunctionCallExpr(call) => self.compile_function_call_expr(call), Expr::FieldAccessExpr(field_access) => self.compile_field_access_expr(field_access), Expr::IndexAccessExpr(index_access) => self.compile_index_access_expr(index_access), + Expr::ExistsExpr(exists) => self.compile_exists_expr(exists), }; self.is_callee = false; diff --git a/crates/rue-compiler/src/compiler/expr/binary_expr.rs b/crates/rue-compiler/src/compiler/expr/binary_expr.rs index 4d2b779..e57ddda 100644 --- a/crates/rue-compiler/src/compiler/expr/binary_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/binary_expr.rs @@ -4,7 +4,7 @@ use rue_parser::{AstNode, BinaryExpr, BinaryOp, Expr}; use crate::{ compiler::Compiler, hir::{BinOp, Hir, Op}, - value::{Guard, Value}, + value::{Guard, TypeOverride, Value}, ErrorKind, HirId, TypeId, }; @@ -172,10 +172,11 @@ impl Compiler<'_> { { if let Some(guard_path) = rhs.guard_path { let then_type = self.builtins.nil; - let else_type = self.db.non_optional(rhs.type_id); - value - .guards - .insert(guard_path, Guard::new(then_type, else_type)); + let else_type = self.db.non_nullable(rhs.type_id); + value.guards.insert( + guard_path, + Guard::new(TypeOverride::new(then_type), TypeOverride::new(else_type)), + ); } } @@ -186,10 +187,11 @@ impl Compiler<'_> { { if let Some(guard_path) = lhs.guard_path.clone() { let then_type = self.builtins.nil; - let else_type = self.db.non_optional(lhs.type_id); - value - .guards - .insert(guard_path, Guard::new(then_type, else_type)); + let else_type = self.db.non_nullable(lhs.type_id); + value.guards.insert( + guard_path, + Guard::new(TypeOverride::new(then_type), TypeOverride::new(else_type)), + ); } } diff --git a/crates/rue-compiler/src/compiler/expr/exists_expr.rs b/crates/rue-compiler/src/compiler/expr/exists_expr.rs index cfd0677..153a317 100644 --- a/crates/rue-compiler/src/compiler/expr/exists_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/exists_expr.rs @@ -1,14 +1,38 @@ -/* -Type::PossiblyUndefined(inner) if field_name.text() == "exists" => { - let maybe_nil_reference = self.db.alloc_hir(Hir::Op(Op::Exists, old_value.hir_id)); - let exists = self.db.alloc_hir(Hir::Op(Op::Listp, maybe_nil_reference)); - let mut new_value = Value::new(exists, self.builtins.bool); - - if let Some(guard_path) = old_value.guard_path { - new_value - .guards - .insert(guard_path, Guard::new(inner, old_value.type_id)); - } - - new_value - } */ +use rue_parser::{AstNode, ExistsExpr}; + +use crate::{ + compiler::Compiler, + hir::{Hir, Op}, + value::{Guard, Mutation, Type, TypeOverride, Value}, + ErrorKind, +}; + +impl Compiler<'_> { + pub fn compile_exists_expr(&mut self, exists: &ExistsExpr) -> Value { + let Some(value) = exists.expr().map(|expr| self.compile_expr(&expr, None)) else { + return self.unknown(); + }; + + let Type::Optional(inner) = self.db.ty(value.type_id).clone() else { + self.db.error( + ErrorKind::InvalidExistanceCheck(self.type_name(value.type_id)), + exists.syntax().text_range(), + ); + return self.unknown(); + }; + + let exists = self.db.alloc_hir(Hir::Op(Op::Listp, value.hir_id)); + let mut new_value = Value::new(exists, self.builtins.bool); + + if let Some(guard_path) = value.guard_path { + let mut unwrap = TypeOverride::new(inner); + unwrap.mutation = Mutation::UnwrapOptional; + new_value.guards.insert( + guard_path, + Guard::new(unwrap, TypeOverride::new(value.type_id)), + ); + } + + new_value + } +} diff --git a/crates/rue-compiler/src/compiler/expr/field_access_expr.rs b/crates/rue-compiler/src/compiler/expr/field_access_expr.rs index 92238f3..cad3921 100644 --- a/crates/rue-compiler/src/compiler/expr/field_access_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/field_access_expr.rs @@ -29,15 +29,14 @@ impl Compiler<'_> { let mut type_id = field_type; if index == struct_type.fields.len() - 1 && struct_type.rest == Rest::Optional { - type_id = self.db.alloc_type(Type::PossiblyUndefined(type_id)); + type_id = self.db.alloc_type(Type::Optional(type_id)); } Value::new( self.compile_index( old_value.hir_id, index, - index == struct_type.fields.len() - 1 - && struct_type.rest == Rest::Spread, + index == struct_type.fields.len() - 1 && struct_type.rest != Rest::Nil, ), type_id, ) @@ -57,14 +56,14 @@ impl Compiler<'_> { let mut type_id = field_type; if index == fields.len() - 1 && variant_type.rest == Rest::Optional { - type_id = self.db.alloc_type(Type::PossiblyUndefined(type_id)); + type_id = self.db.alloc_type(Type::Optional(type_id)); } Value::new( self.compile_index( old_value.hir_id, index, - index == fields.len() - 1 && variant_type.rest == Rest::Spread, + index == fields.len() - 1 && variant_type.rest != Rest::Nil, ), type_id, ) @@ -113,8 +112,9 @@ impl Compiler<'_> { }; if let Some(guard_path) = new_value.guard_path.as_ref() { - if let Some(type_id) = self.symbol_type(guard_path) { - new_value.type_id = type_id; + if let Some(type_override) = self.symbol_type(guard_path) { + new_value.type_id = type_override.type_id; + new_value.hir_id = self.apply_mutation(new_value.hir_id, type_override.mutation); } } diff --git a/crates/rue-compiler/src/compiler/expr/guard_expr.rs b/crates/rue-compiler/src/compiler/expr/guard_expr.rs index 87453da..3e02c20 100644 --- a/crates/rue-compiler/src/compiler/expr/guard_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/guard_expr.rs @@ -4,7 +4,7 @@ use rue_parser::{AstNode, GuardExpr}; use crate::{ compiler::Compiler, hir::{BinOp, Hir, Op}, - value::{Guard, PairType, Type, Value}, + value::{Guard, PairType, Type, TypeOverride, Value}, Comparison, ErrorKind, HirId, TypeId, WarningKind, }; @@ -52,7 +52,10 @@ impl Compiler<'_> { WarningKind::RedundantTypeCheck(self.type_name(from)), text_range, ); - return Some((Guard::new(to, self.builtins.bool), hir_id)); + return Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(self.builtins.bool)), + hir_id, + )); } match (self.db.ty(from).clone(), self.db.ty(to).clone()) { @@ -66,7 +69,13 @@ impl Compiler<'_> { } let hir_id = self.db.alloc_hir(Hir::Op(Op::Listp, hir_id)); - Some((Guard::new(to, self.builtins.bytes), hir_id)) + Some(( + Guard::new( + TypeOverride::new(to), + TypeOverride::new(self.builtins.bytes), + ), + hir_id, + )) } (Type::Any, Type::Bytes) => { let pair_type = self.db.alloc_type(Type::Pair(PairType { @@ -75,7 +84,10 @@ impl Compiler<'_> { })); let is_cons = self.db.alloc_hir(Hir::Op(Op::Listp, hir_id)); let hir_id = self.db.alloc_hir(Hir::Op(Op::Not, is_cons)); - Some((Guard::new(to, pair_type), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(pair_type)), + hir_id, + )) } (Type::List(inner), Type::Pair(PairType { first, rest })) => { if !self.db.compare_type(first, inner).is_equal() { @@ -87,7 +99,10 @@ impl Compiler<'_> { } let hir_id = self.db.alloc_hir(Hir::Op(Op::Listp, hir_id)); - Some((Guard::new(to, self.builtins.nil), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(self.builtins.nil)), + hir_id, + )) } (Type::List(inner), Type::Nil) => { let pair_type = self.db.alloc_type(Type::Pair(PairType { @@ -96,7 +111,10 @@ impl Compiler<'_> { })); let is_cons = self.db.alloc_hir(Hir::Op(Op::Listp, hir_id)); let hir_id = self.db.alloc_hir(Hir::Op(Op::Not, is_cons)); - Some((Guard::new(to, pair_type), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(pair_type)), + hir_id, + )) } (Type::Bytes, Type::Bytes32) => { let strlen = self.db.alloc_hir(Hir::Op(Op::Strlen, hir_id)); @@ -104,7 +122,10 @@ impl Compiler<'_> { let hir_id = self .db .alloc_hir(Hir::BinaryOp(BinOp::Equals, strlen, length)); - Some((Guard::new(to, from), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(from)), + hir_id, + )) } (Type::Bytes, Type::PublicKey) => { let strlen = self.db.alloc_hir(Hir::Op(Op::Strlen, hir_id)); @@ -112,7 +133,10 @@ impl Compiler<'_> { let hir_id = self .db .alloc_hir(Hir::BinaryOp(BinOp::Equals, strlen, length)); - Some((Guard::new(to, from), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(from)), + hir_id, + )) } (Type::Enum(..), Type::EnumVariant(variant_type)) => { if variant_type.enum_type != from { @@ -128,7 +152,10 @@ impl Compiler<'_> { first, variant_type.discriminant, )); - Some((Guard::new(to, from), hir_id)) + Some(( + Guard::new(TypeOverride::new(to), TypeOverride::new(from)), + hir_id, + )) } _ => { self.db.error( diff --git a/crates/rue-compiler/src/compiler/expr/initializer_expr.rs b/crates/rue-compiler/src/compiler/expr/initializer_expr.rs index c6f3ae1..bbb3759 100644 --- a/crates/rue-compiler/src/compiler/expr/initializer_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/initializer_expr.rs @@ -6,7 +6,7 @@ use rue_parser::{AstNode, InitializerExpr, InitializerField}; use crate::{ compiler::Compiler, - hir::{Hir, Op}, + hir::Hir, value::{Rest, Type, Value}, ErrorKind, HirId, TypeId, }; @@ -93,7 +93,7 @@ impl Compiler<'_> { if rest == Rest::Optional && struct_fields.get_index_of(name.text()) == Some(struct_fields.len() - 1) { - optional |= matches!(self.db.ty(value.type_id), Type::PossiblyUndefined(..)); + optional |= matches!(self.db.ty(value.type_id), Type::Optional(..)); value.type_id = self.db.non_undefined(value.type_id); } @@ -152,10 +152,8 @@ impl Compiler<'_> { let field = value.unwrap_or(self.builtins.unknown_hir); - if i == 0 && rest == Rest::Spread { + if i == 0 && (rest == Rest::Spread || (rest == Rest::Optional && optional)) { hir_id = field; - } else if i == 0 && rest == Rest::Optional && optional { - hir_id = self.db.alloc_hir(Hir::Op(Op::Exists, field)); } else { hir_id = self.db.alloc_hir(Hir::Pair(field, hir_id)); } diff --git a/crates/rue-compiler/src/compiler/expr/path_expr.rs b/crates/rue-compiler/src/compiler/expr/path_expr.rs index c4ec507..0a6f7a3 100644 --- a/crates/rue-compiler/src/compiler/expr/path_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/path_expr.rs @@ -62,26 +62,28 @@ impl Compiler<'_> { return self.unknown(); } - let override_type_id = self.symbol_type(&GuardPath::new(symbol_id)); + let type_override = self.symbol_type(&GuardPath::new(symbol_id)); + let override_type_id = type_override.map(|ty| ty.type_id); + let mut reference = self.db.alloc_hir(Hir::Reference(symbol_id, text_range)); + + if let Some(mutation) = type_override.map(|ty| ty.mutation) { + reference = self.apply_mutation(reference, mutation); + } let mut value = match self.db.symbol(symbol_id).clone() { Symbol::Unknown | Symbol::Module(..) => unreachable!(), Symbol::Function(Function { ty, .. }) | Symbol::InlineFunction(Function { ty, .. }) => { let type_id = self.db.alloc_type(Type::Function(ty.clone())); - Value::new( - self.db.alloc_hir(Hir::Reference(symbol_id, text_range)), - override_type_id.unwrap_or(type_id), - ) + Value::new(reference, override_type_id.unwrap_or(type_id)) + } + Symbol::Parameter(type_id) => { + Value::new(reference, override_type_id.unwrap_or(type_id)) } - Symbol::Parameter(type_id) => Value::new( - self.db.alloc_hir(Hir::Reference(symbol_id, text_range)), - override_type_id.unwrap_or(type_id), - ), Symbol::Let(mut value) | Symbol::Const(mut value) | Symbol::InlineConst(mut value) => { if let Some(type_id) = override_type_id { value.type_id = type_id; } - value.hir_id = self.db.alloc_hir(Hir::Reference(symbol_id, text_range)); + value.hir_id = reference; value } }; diff --git a/crates/rue-compiler/src/compiler/item/function_item.rs b/crates/rue-compiler/src/compiler/item/function_item.rs index da2e679..e230040 100644 --- a/crates/rue-compiler/src/compiler/item/function_item.rs +++ b/crates/rue-compiler/src/compiler/item/function_item.rs @@ -73,7 +73,7 @@ impl Compiler<'_> { *self.db.symbol_mut(symbol_id) = Symbol::Parameter(if param.optional().is_some() { // If the parameter is optional, wrap the type in a possibly undefined type. // This prevents referencing the parameter until it's checked for undefined. - self.db.alloc_type(Type::PossiblyUndefined(type_id)) + self.db.alloc_type(Type::Optional(type_id)) } else { // Otherwise, just use the type. type_id diff --git a/crates/rue-compiler/src/compiler/stmt/if_stmt.rs b/crates/rue-compiler/src/compiler/stmt/if_stmt.rs index 523092e..ff930f4 100644 --- a/crates/rue-compiler/src/compiler/stmt/if_stmt.rs +++ b/crates/rue-compiler/src/compiler/stmt/if_stmt.rs @@ -2,7 +2,12 @@ use std::collections::HashMap; use rue_parser::{AstNode, IfStmt}; -use crate::{compiler::Compiler, scope::Scope, value::GuardPath, ErrorKind, HirId, TypeId}; +use crate::{ + compiler::Compiler, + scope::Scope, + value::{GuardPath, TypeOverride}, + ErrorKind, HirId, TypeId, +}; impl Compiler<'_> { /// Compiles an if statement, returning the condition HIR, then block HIR, and else block guards. @@ -10,7 +15,7 @@ impl Compiler<'_> { &mut self, if_stmt: &IfStmt, expected_type: Option, - ) -> (HirId, HirId, HashMap) { + ) -> (HirId, HirId, HashMap) { // Compile the condition expression. let condition = if_stmt .condition() diff --git a/crates/rue-compiler/src/compiler/ty.rs b/crates/rue-compiler/src/compiler/ty.rs index a26b886..adcf983 100644 --- a/crates/rue-compiler/src/compiler/ty.rs +++ b/crates/rue-compiler/src/compiler/ty.rs @@ -6,7 +6,7 @@ use super::Compiler; mod function_type; mod list_type; -mod optional_type; +mod nullable_type; mod pair_type; mod path_type; @@ -19,7 +19,7 @@ impl Compiler<'_> { Type::ListType(list) => self.compile_list_type(&list), Type::FunctionType(function) => self.compile_function_type(&function), Type::PairType(tuple) => self.compile_pair_type(&tuple), - Type::OptionalType(optional) => self.compile_optional_type(&optional), + Type::NullableType(optional) => self.compile_nullable_type(&optional), } } } diff --git a/crates/rue-compiler/src/compiler/ty/optional_type.rs b/crates/rue-compiler/src/compiler/ty/nullable_type.rs similarity index 57% rename from crates/rue-compiler/src/compiler/ty/optional_type.rs rename to crates/rue-compiler/src/compiler/ty/nullable_type.rs index 9d06215..16d25fb 100644 --- a/crates/rue-compiler/src/compiler/ty/optional_type.rs +++ b/crates/rue-compiler/src/compiler/ty/nullable_type.rs @@ -1,21 +1,21 @@ -use rue_parser::{AstNode, OptionalType}; +use rue_parser::{AstNode, NullableType}; use crate::{compiler::Compiler, value::Type, TypeId, WarningKind}; impl Compiler<'_> { - pub fn compile_optional_type(&mut self, optional: &OptionalType) -> TypeId { + pub fn compile_nullable_type(&mut self, optional: &NullableType) -> TypeId { let ty = optional .ty() .map_or(self.builtins.unknown, |ty| self.compile_type(ty)); - if let Type::Optional(inner) = self.db.ty_raw(ty).clone() { + if let Type::Nullable(inner) = self.db.ty_raw(ty).clone() { self.db.warning( - WarningKind::RedundantOptionalType(self.type_name(ty)), + WarningKind::RedundantNullableType(self.type_name(ty)), optional.syntax().text_range(), ); return inner; } - self.db.alloc_type(Type::Optional(ty)) + self.db.alloc_type(Type::Nullable(ty)) } } diff --git a/crates/rue-compiler/src/database.rs b/crates/rue-compiler/src/database.rs index f142d8e..822e066 100644 --- a/crates/rue-compiler/src/database.rs +++ b/crates/rue-compiler/src/database.rs @@ -163,7 +163,6 @@ impl Database { Op::Not => format!("Not({})", self.dbg_hir(*hir_id)), Op::Strlen => format!("Strlen({})", self.dbg_hir(*hir_id)), Op::PubkeyForExp => format!("PubkeyForExp({})", self.dbg_hir(*hir_id)), - Op::Exists => format!("Exists({})", self.dbg_hir(*hir_id)), }, Hir::Raise(hir_id) => format!( "Raise({})", diff --git a/crates/rue-compiler/src/database/type_system.rs b/crates/rue-compiler/src/database/type_system.rs index 5d72e60..c3f948d 100644 --- a/crates/rue-compiler/src/database/type_system.rs +++ b/crates/rue-compiler/src/database/type_system.rs @@ -70,16 +70,16 @@ impl Database { Some(pair.rest) } - pub fn non_optional(&mut self, ty: TypeId) -> TypeId { + pub fn non_nullable(&mut self, ty: TypeId) -> TypeId { match self.ty(ty) { - Type::Optional(inner) => self.non_optional(*inner), + Type::Nullable(inner) => self.non_nullable(*inner), _ => ty, } } pub fn non_undefined(&mut self, ty: TypeId) -> TypeId { match self.ty(ty) { - Type::PossiblyUndefined(inner) => self.non_undefined(*inner), + Type::Optional(inner) => self.non_undefined(*inner), _ => ty, } } @@ -222,7 +222,16 @@ impl Database { self.alloc_type(Type::Function(new_function)) } } - Type::Optional(inner) | Type::PossiblyUndefined(inner) => { + Type::Nullable(inner) => { + let new_inner = self.substitute_type_visitor(inner, substitutions, visited); + + if new_inner == inner { + type_id + } else { + self.alloc_type(Type::Nullable(inner)) + } + } + Type::Optional(inner) => { let new_inner = self.substitute_type_visitor(inner, substitutions, visited); if new_inner == inner { @@ -284,9 +293,7 @@ impl Database { (Type::Unknown, _) | (_, Type::Unknown) => Comparison::Equal, // Possibly undefined is a special type. - (Type::PossiblyUndefined(..), _) | (_, Type::PossiblyUndefined(..)) => { - Comparison::Unrelated - } + (Type::Optional(..), _) | (_, Type::Optional(..)) => Comparison::Unrelated, // These are of course equal atomic types. (Type::Any, Type::Any) => Comparison::Equal, @@ -350,19 +357,19 @@ impl Database { (Type::Bytes32, Type::Bool) => Comparison::Unrelated, (Type::Bytes32, Type::Nil) => Comparison::Unrelated, - // These are the variants of the `Optional` type. - (Type::Nil, Type::Optional(..)) => Comparison::Assignable, - (Type::Optional(lhs), Type::Optional(rhs)) => { + // These are the variants of the `Nullable` type. + (Type::Nil, Type::Nullable(..)) => Comparison::Assignable, + (Type::Nullable(lhs), Type::Nullable(rhs)) => { self.compare_type_visitor(*lhs, *rhs, ctx) } - (_, Type::Optional(inner)) => self.compare_type_visitor(lhs, *inner, ctx), - (Type::Optional(inner), Type::Bytes) => { + (_, Type::Nullable(inner)) => self.compare_type_visitor(lhs, *inner, ctx), + (Type::Nullable(inner), Type::Bytes) => { Comparison::Castable & self.compare_type_visitor(*inner, rhs, ctx) } // TODO: Unions would make this more generalized and useful. // I should add unions back. - (Type::Optional(_inner), _) => Comparison::Unrelated, + (Type::Nullable(_inner), _) => Comparison::Unrelated, // Compare both sides of a `Pair`. (Type::Pair(lhs), Type::Pair(rhs)) => { @@ -496,9 +503,7 @@ impl Database { | Type::Bytes | Type::Bytes32 | Type::PublicKey => false, - Type::Optional(ty) | Type::PossiblyUndefined(ty) => { - self.is_cyclic_visitor(ty, visited_aliases) - } + Type::Nullable(ty) | Type::Optional(ty) => self.is_cyclic_visitor(ty, visited_aliases), } } } diff --git a/crates/rue-compiler/src/dependency_graph.rs b/crates/rue-compiler/src/dependency_graph.rs index dd3955e..b53113f 100644 --- a/crates/rue-compiler/src/dependency_graph.rs +++ b/crates/rue-compiler/src/dependency_graph.rs @@ -136,7 +136,7 @@ impl<'a> GraphBuilder<'a> { let environment_id = self.db.alloc_env(Environment::function( parameters, - function.ty.rest == Rest::Spread, + function.ty.rest != Rest::Nil, )); self.graph diff --git a/crates/rue-compiler/src/error.rs b/crates/rue-compiler/src/error.rs index 8a89df6..8e6b061 100644 --- a/crates/rue-compiler/src/error.rs +++ b/crates/rue-compiler/src/error.rs @@ -49,7 +49,7 @@ pub enum WarningKind { UnusedEnumVariant(String), UnusedStruct(String), UnusedTypeAlias(String), - RedundantOptionalType(String), + RedundantNullableType(String), RedundantTypeCheck(String), } @@ -67,8 +67,8 @@ impl fmt::Display for WarningKind { Self::UnusedEnumVariant(name) => format!("Unused enum variant `{name}`."), Self::UnusedStruct(name) => format!("Unused struct `{name}`."), Self::UnusedTypeAlias(name) => format!("Unused type alias `{name}`."), - Self::RedundantOptionalType(ty) => { - format!("This has no effect, since `{ty}` is already an optional type.") + Self::RedundantNullableType(ty) => { + format!("This has no effect, since `{ty}` is already a nullable type.") } Self::RedundantTypeCheck(ty) => format!( "It's redundant to guard against `{ty}`, since the value already has that type." @@ -147,6 +147,7 @@ pub enum ErrorKind { UnsupportedTypeGuard(String, String), NonAnyPairTypeGuard, NonListPairTypeGuard, + InvalidExistanceCheck(String), // Blocks. ImplicitReturnInIf, @@ -296,6 +297,7 @@ impl fmt::Display for ErrorKind { Self::UnsupportedTypeGuard(from, to) => format!("Cannot check type `{from}` against `{to}`."), Self::NonAnyPairTypeGuard => "Cannot check `Any` against pair types other than `(Any, Any)`.".to_string(), Self::NonListPairTypeGuard => "Cannot check `T[]` against pair types other than `(T, T[])`.".to_string(), + Self::InvalidExistanceCheck(ty) => format!("Cannot check existence of value with type `{ty}`, since it can't be undefined."), // Blocks. Self::ImplicitReturnInIf => formatdoc!(" diff --git a/crates/rue-compiler/src/hir.rs b/crates/rue-compiler/src/hir.rs index 9344e77..a379a4d 100644 --- a/crates/rue-compiler/src/hir.rs +++ b/crates/rue-compiler/src/hir.rs @@ -27,7 +27,6 @@ pub enum Op { Listp, Strlen, PubkeyForExp, - Exists, Not, } diff --git a/crates/rue-compiler/src/optimizer.rs b/crates/rue-compiler/src/optimizer.rs index d001a5d..9ae2d76 100644 --- a/crates/rue-compiler/src/optimizer.rs +++ b/crates/rue-compiler/src/optimizer.rs @@ -28,7 +28,6 @@ impl<'a> Optimizer<'a> { Mir::Op(Op::Listp, value) => self.opt_listp(env_id, value), Mir::Op(Op::Strlen, value) => self.opt_strlen(env_id, value), Mir::Op(Op::PubkeyForExp, value) => self.opt_pubkey_for_exp(env_id, value), - Mir::Op(Op::Exists, value) => self.opt_check_exists(env_id, value), Mir::Raise(value) => self.opt_raise(env_id, value), Mir::BinaryOp(op, lhs, rhs) => { let handler = match op { @@ -129,29 +128,6 @@ impl<'a> Optimizer<'a> { self.db.alloc_lir(Lir::Path(path)) } - fn opt_check_exists(&mut self, env_id: EnvironmentId, mir_id: MirId) -> LirId { - let value = self.opt_mir(env_id, mir_id); - let Lir::Path(path) = self.db.lir(value).clone() else { - panic!("invalid LIR, expected path for existence check"); - }; - self.db.alloc_lir(Lir::Path(Self::pack_bits(path))) - } - - fn pack_bits(mut n: u32) -> u32 { - let mut result = 0; - let mut position = 0; - - while n > 0 { - if n & 1 != 0 { - result |= 1 << position; - position += 1; - } - n >>= 1; - } - - result - } - fn opt_pair(&mut self, env_id: EnvironmentId, first: MirId, rest: MirId) -> LirId { let first = self.opt_mir(env_id, first); let rest = self.opt_mir(env_id, rest); diff --git a/crates/rue-compiler/src/value.rs b/crates/rue-compiler/src/value.rs index 18ba279..64db5be 100644 --- a/crates/rue-compiler/src/value.rs +++ b/crates/rue-compiler/src/value.rs @@ -28,14 +28,14 @@ impl Value { } } - pub fn then_guards(&self) -> HashMap { + pub fn then_guards(&self) -> HashMap { self.guards .iter() .map(|(k, v)| (k.clone(), v.then_type)) .collect() } - pub fn else_guards(&self) -> HashMap { + pub fn else_guards(&self) -> HashMap { self.guards .iter() .map(|(k, v)| (k.clone(), v.else_type)) diff --git a/crates/rue-compiler/src/value/guard.rs b/crates/rue-compiler/src/value/guard.rs index 1389e33..351a083 100644 --- a/crates/rue-compiler/src/value/guard.rs +++ b/crates/rue-compiler/src/value/guard.rs @@ -4,12 +4,12 @@ use crate::TypeId; #[derive(Debug, Clone, Copy)] pub struct Guard { - pub then_type: TypeId, - pub else_type: TypeId, + pub then_type: TypeOverride, + pub else_type: TypeOverride, } impl Guard { - pub fn new(then_type: TypeId, else_type: TypeId) -> Self { + pub fn new(then_type: TypeOverride, else_type: TypeOverride) -> Self { Self { then_type, else_type, @@ -27,3 +27,25 @@ impl Not for Guard { } } } + +#[derive(Debug, Clone, Copy)] +pub struct TypeOverride { + pub type_id: TypeId, + pub mutation: Mutation, +} + +impl TypeOverride { + pub fn new(type_id: TypeId) -> Self { + Self { + type_id, + mutation: Mutation::None, + } + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub enum Mutation { + #[default] + None, + UnwrapOptional, +} diff --git a/crates/rue-compiler/src/value/ty.rs b/crates/rue-compiler/src/value/ty.rs index 640d662..e724aed 100644 --- a/crates/rue-compiler/src/value/ty.rs +++ b/crates/rue-compiler/src/value/ty.rs @@ -20,8 +20,8 @@ pub enum Type { EnumVariant(EnumVariantType), Function(FunctionType), Alias(TypeId), + Nullable(TypeId), Optional(TypeId), - PossiblyUndefined(TypeId), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/rue-parser/src/ast.rs b/crates/rue-parser/src/ast.rs index 2e5a86e..c99eb2f 100644 --- a/crates/rue-parser/src/ast.rs +++ b/crates/rue-parser/src/ast.rs @@ -97,7 +97,8 @@ ast_enum!( IfExpr, FunctionCallExpr, FieldAccessExpr, - IndexAccessExpr + IndexAccessExpr, + ExistsExpr ); ast_node!(PathExpr); ast_node!(InitializerExpr); @@ -116,6 +117,7 @@ ast_node!(FunctionCallExpr); ast_node!(FunctionCallArg); ast_node!(FieldAccessExpr); ast_node!(IndexAccessExpr); +ast_node!(ExistsExpr); ast_node!(LambdaExpr); ast_node!(LambdaParam); @@ -126,7 +128,7 @@ ast_enum!( ListType, PairType, FunctionType, - OptionalType + NullableType ); ast_node!(PathType); ast_node!(ListType); @@ -134,7 +136,7 @@ ast_node!(ListTypeItem); ast_node!(PairType); ast_node!(FunctionType); ast_node!(FunctionTypeParam); -ast_node!(OptionalType); +ast_node!(NullableType); ast_enum!(Stmt, LetStmt, IfStmt, ReturnStmt, RaiseStmt, AssertStmt, AssumeStmt); ast_node!(LetStmt); @@ -821,6 +823,12 @@ impl IndexAccessExpr { } } +impl ExistsExpr { + pub fn expr(&self) -> Option { + self.syntax().children().find_map(Expr::cast) + } +} + impl PathType { pub fn idents(&self) -> Vec { self.syntax() @@ -900,7 +908,7 @@ impl FunctionTypeParam { } } -impl OptionalType { +impl NullableType { pub fn ty(&self) -> Option { self.syntax().children().find_map(Type::cast) } diff --git a/crates/rue-parser/src/grammar.rs b/crates/rue-parser/src/grammar.rs index 78d61fc..da86296 100644 --- a/crates/rue-parser/src/grammar.rs +++ b/crates/rue-parser/src/grammar.rs @@ -408,6 +408,10 @@ fn expr_binding_power(p: &mut Parser<'_>, minimum_binding_power: u8, allow_initi } p.expect(SyntaxKind::CloseParen); p.finish(); + } else if p.at(SyntaxKind::Question) { + p.start_at(checkpoint, SyntaxKind::ExistsExpr); + p.bump(); + p.finish(); } else if p.at(SyntaxKind::Dot) { p.start_at(checkpoint, SyntaxKind::FieldAccessExpr); p.bump(); @@ -588,7 +592,7 @@ fn ty(p: &mut Parser<'_>) { p.expect(SyntaxKind::CloseBracket); p.finish(); } else if p.at(SyntaxKind::Question) { - p.start_at(checkpoint, SyntaxKind::OptionalType); + p.start_at(checkpoint, SyntaxKind::NullableType); p.bump(); p.finish(); } else { diff --git a/crates/rue-parser/src/syntax_kind.rs b/crates/rue-parser/src/syntax_kind.rs index b64016d..643e5d7 100644 --- a/crates/rue-parser/src/syntax_kind.rs +++ b/crates/rue-parser/src/syntax_kind.rs @@ -121,6 +121,7 @@ pub enum SyntaxKind { FunctionCallArg, FieldAccessExpr, IndexAccessExpr, + ExistsExpr, PathType, ListType, @@ -128,7 +129,7 @@ pub enum SyntaxKind { PairType, FunctionType, FunctionTypeParam, - OptionalType, + NullableType, GenericTypes, } @@ -242,13 +243,14 @@ impl fmt::Display for SyntaxKind { Self::FunctionCallArg => "function call argument", Self::FieldAccessExpr => "field access expression", Self::IndexAccessExpr => "index access expression", + Self::ExistsExpr => "exists expression", Self::PathType => "path type", Self::ListType => "list type", Self::ListTypeItem => "list type item", Self::PairType => "pair type", Self::FunctionType => "function type", Self::FunctionTypeParam => "function type parameter", - Self::OptionalType => "optional type", + Self::NullableType => "nullable type", Self::GenericTypes => "generic types", } diff --git a/tests/struct/struct_optional.rue b/tests/struct/struct_optional.rue index a05c2e9..427cb29 100644 --- a/tests/struct/struct_optional.rue +++ b/tests/struct/struct_optional.rue @@ -11,7 +11,7 @@ fun main() -> Int { } fun sum(point: Point) -> Int { - if point.z.exists { + if point.z? { point.x + point.y + point.z } else { point.x + point.y