diff --git a/src/men-sharp-compiler/tests/codegen.rs b/src/men-sharp-compiler/tests/codegen.rs index bfb7bd5..ac8649c 100644 --- a/src/men-sharp-compiler/tests/codegen.rs +++ b/src/men-sharp-compiler/tests/codegen.rs @@ -16065,3 +16065,154 @@ fn static_field_initializers_are_part_of_their_classs_initialization() { }; assert_eq!(int_of(&emulator, "x"), 11); } + +#[test] +fn compound_constants_keep_types_and_declaration_scope() { + // The RHS must be evaluated in its declaration's scope, with its real + // signedness and width, even before that initializer has been checked. + for expression in [ + "Other.N + 1", + "sizeof(System.Int32)", + "(int)(0xffffffffu >> 31)", + "(int)(0xffffffffffffffffUL >> 63)", + "(int)(((long)1 << 32) >> 32)", + "(int)(~0xfffffffeu)", + "(int)(~0xfffffffffffffffeUL)", + "true ? 1 : 2", + "Other.Flag && Other.N == 2 ? 1 : 2", + ] { + for target in ["sbyte", "byte", "short", "ushort", "uint", "ulong"] { + for before in [true, false] { + let constant = format!("const int K = {expression};"); + let method = format!("public void Interact() {{ {target} b = 1; b += K; }}"); + let body = if before { + format!("{constant} {method}") + } else { + format!("{method} {constant}") + }; + let source = format!( + "using MenSharp; public class Probe : MenSharpBehaviour {{ {body} }} + public class Other {{ public const int N = 2; public const bool Flag = true; }}" + ); + let Some(messages) = codegen_messages(&source, "Probe") else { + return; + }; + assert!( + messages.is_empty(), + "{target}: {expression}, before={before}: {messages:?}" + ); + } + } + } +} + +#[test] +fn compound_constants_resolve_aliases_in_the_declaring_file() { + // The caller has a different alias and a shadowing constant: neither + // may change a constant declared in another file or a nested type. + let mut sources = vec![ + SourceCode::new( + "caller.cs", + r#" + using MenSharp; + using Number = System.Int64; + public class Probe : MenSharpBehaviour { + const int N = 1000; + public void Interact() { byte b = 0; b += Values.Outer.Inner.K; } + } + "#, + ), + SourceCode::new( + "values.cs", + r#" + namespace Values { + using Number = System.Int32; + public class Outer { + public const int N = 2; + public class Inner { public const int K = Outer.N + sizeof(Number); } + } + } + "#, + ), + ]; + sources.extend(Compiler::corlib_sources()); + let Some(dir) = dotnet_shared_dir() else { + return; + }; + let compiler = Compiler::new(CompilerSettings::default()).unwrap(); + let bytes = vec![std::fs::read(dir.join("System.Private.CoreLib.dll")).unwrap()]; + let references = compiler.load_references(&bytes).unwrap(); + let files = compiler.parse(sources); + let declarations = compiler.collect_declarations(&files); + let signatures = compiler.resolve_signatures(&declarations, &references); + let bodies = compiler.check_bodies(&declarations, &signatures, &references); + assert!(bodies.errors.is_empty(), "{:#?}", bodies.errors); +} + +#[test] +fn compound_constants_do_not_admit_nonconstants_or_out_of_range_values() { + // A selected branch fitting byte does not make a nonconstant expression + // constant. Cycles must also terminate instead of recursively rebinding. + for declaration in [ + "const int K = true ? 256 : 1;", + "const int K = false ? 1 : -1;", + "const int K = (int)(0xffffffffu >> 23);", + "static readonly int K = 1;", + "const bool K = true;", + "static int X = 1; const int K = true ? 1 : X;", + "const int K = L; const int L = K;", + ] { + let mut sources = vec![SourceCode::new( + "test.cs", + format!( + "using MenSharp; public class Probe : MenSharpBehaviour {{ + public void Interact() {{ byte b = 0; b += K; }} {declaration} + }}" + ), + )]; + sources.extend(Compiler::corlib_sources()); + let Some(dir) = dotnet_shared_dir() else { + return; + }; + let compiler = Compiler::new(CompilerSettings::default()).unwrap(); + let bytes = vec![std::fs::read(dir.join("System.Private.CoreLib.dll")).unwrap()]; + let references = compiler.load_references(&bytes).unwrap(); + let files = compiler.parse(sources); + let declarations = compiler.collect_declarations(&files); + let signatures = compiler.resolve_signatures(&declarations, &references); + let bodies = compiler.check_bodies(&declarations, &signatures, &references); + assert!( + bodies.errors.iter().any(|error| matches!( + error.kind, + men_sharp_semantics::SemanticErrorKind::TypeMismatch { .. } + | men_sharp_semantics::SemanticErrorKind::InvalidOperator { .. } + )), + "{declaration}: {:#?}", + bodies.errors + ); + } +} + +#[test] +fn compound_constants_use_boolean_locals_without_converting_bool_to_int() { + // A const bool may select an integral constant, but the bool itself + // must never be accepted as an integral constant conversion. + let Some(messages) = codegen_messages( + r#" + using MenSharp; + public class Probe : MenSharpBehaviour { + public void Interact() { + const bool flag = true; + const bool other = !flag; + const int step = flag && !other ? 1 : 2; + byte b = 0; + b += step; + } + } + "#, + "Probe", + ) else { + return; + }; + assert!(messages.is_empty(), "{messages:?}"); +} diff --git a/src/men-sharp-semantics/src/semantics/check.rs b/src/men-sharp-semantics/src/semantics/check.rs index 5eafe31..a413812 100644 --- a/src/men-sharp-semantics/src/semantics/check.rs +++ b/src/men-sharp-semantics/src/semantics/check.rs @@ -337,75 +337,7 @@ pub fn check_file( ) -> BodyCheck { let file = &declarations.files[file_index]; - let mut checker = Checker { - resolver: Resolver { - declarations, - external, - file: file.file, - out: Signatures::default(), - }, - signatures, - scopes: Vec::new(), - type_stack: Vec::new(), - locals: Vec::new(), - this_type: None, - static_context: true, - return_type: Type::Void, - lambda_probe_returns: None, - lambda_stack: Vec::new(), - current_member: None, - captures: HashMap::default(), - captured_locals: HashMap::default(), - capture_types: HashMap::default(), - local_functions: HashMap::default(), - local_calls: HashMap::default(), - declared_names: HashMap::default(), - static_local_functions: HashSet::default(), - local_order: 0, - local_function_order: HashMap::default(), - expression_types: HashMap::default(), - numeric_promotions: HashMap::default(), - attribute_types: HashMap::default(), - targets: HashMap::default(), - pattern_inputs: HashMap::default(), - enumerations: HashMap::default(), - disposals: HashMap::default(), - constructor_chains: HashMap::default(), - catch_depth: 0, - in_async: false, - awaits: HashMap::default(), - iterator_element: None, - yield_seen: false, - value_return_seen: false, - guarded_depth: 0, - catch_depth_for_yield: 0, - finally_depth: 0, - iterators: HashSet::default(), - }; - - // rebuild the same file scope signature resolution used - checker.scopes.push(NamespaceScope { - path: Vec::new(), - symbol: Some(declarations.table.root()), - usings: Vec::new(), - }); - let mut root_usings = Vec::new(); - for (_, using) in declarations.global_usings() { - if let Some(resolved) = checker.resolve_using(using) { - root_usings.push(resolved); - } - } - for using in &file.usings { - if using.global.is_none() - && let Some(resolved) = checker.resolve_using(using) - { - root_usings.push(resolved); - } - } - // using-target resolution errors were already reported by the resolve phase; - // this rebuild must not duplicate them - checker.resolver.out.errors.clear(); - checker.scopes[0].usings = root_usings; + let mut checker = Checker::for_file(declarations, signatures, external, file_index); checker.walk_nodes(&file.members); checker.close_captures(); @@ -576,6 +508,8 @@ struct LocalFunctionEntry<'ast> { struct Checker<'a, 'ast> { resolver: Resolver<'a, 'ast>, + /// Bounds recursive const-field binding as well as value evaluation. + constant_depth: usize, signatures: &'a Signatures, scopes: Vec>, type_stack: Vec, @@ -686,3 +620,86 @@ fn type_parameter_leaves(ty: &Type) -> usize { _ => 0, } } + +impl<'a, 'ast> Checker<'a, 'ast> { + fn for_file( + declarations: &'a Declarations<'ast>, + signatures: &'a Signatures, + external: &'a dyn ExternalTypes, + file_index: usize, + ) -> Self { + let file = &declarations.files[file_index]; + let mut checker = Checker { + resolver: Resolver { + declarations, + external, + file: file.file, + out: Signatures::default(), + }, + signatures, + constant_depth: 64, + scopes: Vec::new(), + type_stack: Vec::new(), + locals: Vec::new(), + this_type: None, + static_context: true, + return_type: Type::Void, + lambda_probe_returns: None, + lambda_stack: Vec::new(), + current_member: None, + captures: HashMap::default(), + captured_locals: HashMap::default(), + capture_types: HashMap::default(), + local_functions: HashMap::default(), + local_calls: HashMap::default(), + declared_names: HashMap::default(), + static_local_functions: HashSet::default(), + local_order: 0, + local_function_order: HashMap::default(), + expression_types: HashMap::default(), + numeric_promotions: HashMap::default(), + attribute_types: HashMap::default(), + targets: HashMap::default(), + pattern_inputs: HashMap::default(), + enumerations: HashMap::default(), + disposals: HashMap::default(), + constructor_chains: HashMap::default(), + catch_depth: 0, + in_async: false, + awaits: HashMap::default(), + iterator_element: None, + yield_seen: false, + value_return_seen: false, + guarded_depth: 0, + catch_depth_for_yield: 0, + finally_depth: 0, + iterators: HashSet::default(), + }; + + // rebuild the same file scope signature resolution used + checker.scopes.push(NamespaceScope { + path: Vec::new(), + symbol: Some(declarations.table.root()), + usings: Vec::new(), + }); + let mut root_usings = Vec::new(); + for (_, using) in declarations.global_usings() { + if let Some(resolved) = checker.resolve_using(using) { + root_usings.push(resolved); + } + } + for using in &file.usings { + if using.global.is_none() + && let Some(resolved) = checker.resolve_using(using) + { + root_usings.push(resolved); + } + } + // using-target resolution errors were already reported by the resolve phase; + // this rebuild must not duplicate them + checker.resolver.out.errors.clear(); + checker.scopes[0].usings = root_usings; + + checker + } +} diff --git a/src/men-sharp-semantics/src/semantics/check/checker.rs b/src/men-sharp-semantics/src/semantics/check/checker.rs index de60c6e..9af0e1b 100644 --- a/src/men-sharp-semantics/src/semantics/check/checker.rs +++ b/src/men-sharp-semantics/src/semantics/check/checker.rs @@ -303,10 +303,14 @@ impl<'a, 'ast> Checker<'a, 'ast> { /// Keep values only for const locals, never for ordinary variables whose /// initializer happens to be a literal. pub(super) fn integer_constant(&self, expression: &Expression<'ast, 'ast>) -> Option { - self.integer_constant_inner(expression, 64) + let ty = self.expression_types.get(&EntityID::from(expression))?; + self.system() + .numeric_kind(ty) + .filter(|kind| kind.is_integral())?; + self.constant_value_inner(expression, self.constant_depth) } - fn integer_constant_inner( + pub(super) fn constant_value_inner( &self, expression: &Expression<'ast, 'ast>, depth: usize, @@ -315,11 +319,13 @@ impl<'a, 'ast> Checker<'a, 'ast> { if depth == 0 { return None; } - let Some(ty) = self.expression_types.get(&EntityID::from(expression)) else { - return super::exhaustive::integer_literal_value(expression).map(i128::from); - }; - let kind = self.system().numeric_kind(ty)?; - if !kind.is_integral() { + let ty = self.expression_types.get(&EntityID::from(expression))?; + let boolean = *ty == self.corlib("Boolean"); + let kind = self + .system() + .numeric_kind(ty) + .filter(|kind| kind.is_integral()); + if kind.is_none() && !boolean { return None; } let value = match expression { @@ -348,6 +354,8 @@ impl<'a, 'ast> Checker<'a, 'ast> { match member.constant.as_ref()? { crate::ExternalConstant::Int(v) => i128::from(*v), crate::ExternalConstant::UInt(v) => i128::from(*v), + crate::ExternalConstant::Boolean(v) => i128::from(*v), + crate::ExternalConstant::Char(v) => i128::from(*v as u32), _ => return None, } } @@ -359,7 +367,7 @@ impl<'a, 'ast> Checker<'a, 'ast> { } Expression::Primary(primary) if primary.chain.is_empty() => match &primary.left { PrimaryLeft::Parenthesized { expression, .. } => { - self.integer_constant_inner(expression, depth - 1)? + self.constant_value_inner(expression, depth - 1)? } PrimaryLeft::Identifier { name, .. } => { self.locals @@ -372,23 +380,30 @@ impl<'a, 'ast> Checker<'a, 'ast> { target_type: Ok(target_type), .. } => i128::from(self.sizeof_constant(target_type)?), - _ => i128::from(super::exhaustive::integer_literal_value(expression)?), + PrimaryLeft::Literal(LiteralExpression::True(_)) => 1, + PrimaryLeft::Literal(LiteralExpression::False(_)) => 0, + _ => super::exhaustive::wide_integer_literal_value(expression)?, }, Expression::Cast(cast) => { - self.integer_constant_inner(cast.value.as_ref().ok()?, depth - 1)? + self.constant_value_inner(cast.value.as_ref().ok()?, depth - 1)? } Expression::Unary(unary) => { - let value = self.integer_constant_inner(unary.operand.as_ref().ok()?, depth - 1)?; + let value = self.constant_value_inner(unary.operand.as_ref().ok()?, depth - 1)?; match unary.operator.value { UnaryOperator::Plus => value, UnaryOperator::Minus => value.checked_neg()?, - UnaryOperator::BitwiseNot => !value, + UnaryOperator::BitwiseNot => match kind? { + UInt32 => i128::from(!(value as u32)), + UInt64 => i128::from(!(value as u64)), + _ => !value, + }, + UnaryOperator::Not => i128::from(value == 0), _ => return None, } } Expression::Binary(binary) => { - let left = self.integer_constant_inner(&binary.left, depth - 1)?; - let right = self.integer_constant_inner(binary.right.as_ref().ok()?, depth - 1)?; + let left = self.constant_value_inner(&binary.left, depth - 1)?; + let right = self.constant_value_inner(binary.right.as_ref().ok()?, depth - 1)?; match binary.operator.value { BinaryOperator::Add => left.checked_add(right)?, BinaryOperator::Subtract => left.checked_sub(right)?, @@ -398,9 +413,18 @@ impl<'a, 'ast> Checker<'a, 'ast> { BinaryOperator::BitwiseAnd => left & right, BinaryOperator::BitwiseOr => left | right, BinaryOperator::BitwiseXor => left ^ right, + BinaryOperator::Equal => i128::from(left == right), + BinaryOperator::NotEqual => i128::from(left != right), + BinaryOperator::LessThan => i128::from(left < right), + BinaryOperator::LessThanEqual => i128::from(left <= right), + BinaryOperator::GreaterThan => i128::from(left > right), + BinaryOperator::GreaterThanEqual => i128::from(left >= right), + BinaryOperator::LogicalAnd => i128::from(left != 0 && right != 0), + BinaryOperator::LogicalOr => i128::from(left != 0 || right != 0), BinaryOperator::LeftShift | BinaryOperator::RightShift | BinaryOperator::UnsignedRightShift => { + let kind = kind?; let bits = if matches!(kind, Int64 | UInt64) { 64 } else { @@ -428,9 +452,22 @@ impl<'a, 'ast> Checker<'a, 'ast> { _ => return None, } } + Expression::Conditional(conditional) => { + // Both branches must be constant, including the unselected one. + let condition = self.constant_value_inner(&conditional.condition, depth - 1)?; + let yes = + self.constant_value_inner(conditional.then_value.as_ref().ok()?, depth - 1)?; + let no = + self.constant_value_inner(conditional.else_value.as_ref().ok()?, depth - 1)?; + if condition != 0 { yes } else { no } + } _ => return None, }; - integer_fits(kind, value).then_some(value) + if boolean { + matches!(value, 0 | 1).then_some(value) + } else { + integer_fits(kind?, value).then_some(value) + } } /// The initializer expression of a source `const` field, or `None` for @@ -452,215 +489,38 @@ impl<'a, 'ast> Checker<'a, 'ast> { }) } - /// The value of a source `const` field. Its initializer may not have - /// been checked yet — it sits below the use, or in another class — so - /// an unchecked one is evaluated structurally against the field's - /// declared type rather than through the types recorded for it (which - /// made the answer depend on declaration order). + /// Bind an as-yet unchecked initializer in its own file/type scope. + /// Reusing the normal checker preserves aliases, qualified names and + /// integer promotion; a structural evaluator used to guess these from + /// syntax and made constant conversion depend on declaration order. fn source_const_value(&self, field: SymbolId, depth: usize) -> Option { - let value = self.const_initializer(field)?; - if self.expression_types.contains_key(&EntityID::from(value)) { - return self.integer_constant_inner(value, depth); - } - let kind = match self.signatures.members.get(&field) { - Some(crate::types::MemberSignature::Field(ty)) => self.system().numeric_kind(ty)?, - _ => return None, - }; - let owner = self.resolver.declarations.table.symbol(field).parent; - let value = self.untyped_integer_constant(value, owner, depth)?; - integer_fits(kind, value).then_some(value) - } - - /// An integer constant expression no type was recorded for: literals, - /// parentheses, unary and binary operators, casts (their value; C# - /// rejects an out-of-range constant cast without `unchecked` anyway), - /// and names of `const` fields of `owner` or a type enclosing it. - fn untyped_integer_constant( - &self, - expression: &Expression<'ast, 'ast>, - owner: Option, - depth: usize, - ) -> Option { if depth == 0 { return None; } - match expression { - Expression::Primary(primary) if primary.chain.is_empty() => match &primary.left { - PrimaryLeft::Parenthesized { expression, .. } => { - self.untyped_integer_constant(expression, owner, depth - 1) - } - PrimaryLeft::Identifier { - name, - generics: None, - .. - } => { - let field = self.const_field_named(owner, name.value)?; - self.source_const_value(field, depth - 1) - } - PrimaryLeft::Literal(_) => { - super::exhaustive::integer_literal_value(expression).map(i128::from) - } - PrimaryLeft::Sizeof { - target_type: Ok(target_type), - .. - } => self.sizeof_constant(target_type).map(i128::from), - _ => None, - }, - Expression::Cast(cast) => { - self.untyped_integer_constant(cast.value.as_ref().ok()?, owner, depth - 1) - } - Expression::Unary(unary) => { - let value = - self.untyped_integer_constant(unary.operand.as_ref().ok()?, owner, depth - 1)?; - match unary.operator.value { - UnaryOperator::Plus => Some(value), - UnaryOperator::Minus => value.checked_neg(), - UnaryOperator::BitwiseNot => Some(!value), - _ => None, - } - } - Expression::Binary(binary) => { - let right_expression = binary.right.as_ref().ok()?; - let left = self.untyped_integer_constant(&binary.left, owner, depth - 1)?; - let right = self.untyped_integer_constant(right_expression, owner, depth - 1)?; - match binary.operator.value { - BinaryOperator::Add => left.checked_add(right), - BinaryOperator::Subtract => left.checked_sub(right), - BinaryOperator::Multiply => left.checked_mul(right), - BinaryOperator::Divide => left.checked_div(right), - BinaryOperator::Modulo => left.checked_rem(right), - BinaryOperator::BitwiseAnd => Some(left & right), - BinaryOperator::BitwiseOr => Some(left | right), - BinaryOperator::BitwiseXor => Some(left ^ right), - operator @ (BinaryOperator::LeftShift - | BinaryOperator::RightShift - | BinaryOperator::UnsignedRightShift) => { - // the left operand's own type sets the width, as in - // C#: `1 << 33` is an int shift by 1, `1L << 33` not - let wide = self.untyped_is_long(&binary.left, owner, depth - 1); - let bits: u32 = if wide { 64 } else { 32 }; - let count = (right as u32) & (bits - 1); - Some(match (operator, wide) { - (BinaryOperator::LeftShift, false) => { - i128::from((left as i32).wrapping_shl(count)) - } - (BinaryOperator::LeftShift, true) => { - i128::from((left as i64).wrapping_shl(count)) - } - (BinaryOperator::UnsignedRightShift, false) => { - i128::from((left as u32) >> count) - } - (BinaryOperator::UnsignedRightShift, true) => { - i128::from((left as u64) >> count) - } - (_, false) => i128::from((left as i32) >> count), - (_, true) => i128::from((left as i64) >> count), - }) - } - _ => None, - } - } - _ => None, - } - } - - /// Is an unchecked integer constant expression a `long`/`ulong` rather - /// than an `int`/`uint`? A suffixed or out-of-uint-range literal, or a - /// const field declared so; for an operator, either operand. - fn untyped_is_long( - &self, - expression: &Expression<'ast, 'ast>, - owner: Option, - depth: usize, - ) -> bool { - use crate::types::conversions::NumericKind::{Int64, UInt64}; - if depth == 0 { - return false; - } - match expression { - Expression::Primary(primary) if primary.chain.is_empty() => match &primary.left { - PrimaryLeft::Parenthesized { expression, .. } => { - self.untyped_is_long(expression, owner, depth - 1) - } - PrimaryLeft::Literal(LiteralExpression::Integer(text)) => { - let suffixed = text.value.to_ascii_lowercase().contains('l'); - suffixed - || super::exhaustive::integer_literal_value(expression) - .is_none_or(|value| u32::try_from(value).is_err()) - } - PrimaryLeft::Identifier { name, .. } => self - .const_field_named(owner, name.value) - .and_then(|field| match self.signatures.members.get(&field) { - Some(crate::types::MemberSignature::Field(ty)) => { - self.system().numeric_kind(ty) - } - _ => None, - }) - .is_some_and(|kind| matches!(kind, Int64 | UInt64)), - _ => false, - }, - Expression::Unary(unary) => unary - .operand - .as_ref() - .is_ok_and(|operand| self.untyped_is_long(operand, owner, depth - 1)), - Expression::Binary(binary) => { - let shift = matches!( - binary.operator.value, - BinaryOperator::LeftShift - | BinaryOperator::RightShift - | BinaryOperator::UnsignedRightShift - ); - self.untyped_is_long(&binary.left, owner, depth - 1) - || (!shift - && binary - .right - .as_ref() - .is_ok_and(|right| self.untyped_is_long(right, owner, depth - 1))) - } - _ => false, - } - } - - /// The value of `sizeof(T)`: from the type the checker resolved for it, - /// or — in an initializer not checked yet — from the keyword spelled. - fn sizeof_constant(&self, target: &men_sharp_parser::ast::TypeRef<'ast, 'ast>) -> Option { - use men_sharp_parser::ast::{PredefinedType as P, TypeRefBase}; - if let Some(ty) = self.resolver.out.type_of.get(&EntityID::from(target)) { - return self.system().sizeof_value(ty); + let value = self.const_initializer(field)?; + if self.expression_types.contains_key(&EntityID::from(value)) { + return self.constant_value_inner(value, depth); } - let TypeRefBase::Predefined(predefined) = &target.base else { - return None; - }; - if !target.suffixes.is_empty() { + let declarations = self.resolver.declarations; + let file = declarations.table.symbol(field).declarations.first()?.file; + let index = declarations + .files + .iter() + .position(|candidate| candidate.file == file)?; + let mut checker = + Self::for_file(declarations, self.signatures, self.resolver.external, index); + checker.constant_depth = depth - 1; + if !checker.bind_constant_nodes(&declarations.files[index].members, field) + || !checker.resolver.out.errors.is_empty() + { return None; } - Some(match predefined.value { - P::Bool | P::Byte | P::Sbyte => 1, - P::Char | P::Short | P::Ushort => 2, - P::Int | P::Uint | P::Float => 4, - P::Long | P::Ulong | P::Double => 8, - P::Decimal => 16, - _ => return None, - }) + checker.constant_value_inner(value, depth - 1) } - /// A `const` field called `name` on `owner` or a type enclosing it. - fn const_field_named(&self, owner: Option, name: &str) -> Option { - let table = &self.resolver.declarations.table; - let mut current = owner; - while let Some(ty) = current { - let symbol = table.symbol(ty); - if let Some(field) = symbol - .members_named(name) - .iter() - .copied() - .find(|&member| self.const_initializer(member).is_some()) - { - return Some(field); - } - current = symbol.parent; - } - None + fn sizeof_constant(&self, target: &men_sharp_parser::ast::TypeRef<'ast, 'ast>) -> Option { + let ty = self.resolver.out.type_of.get(&EntityID::from(target))?; + self.system().sizeof_value(ty) } /// The constant-expression allowance, without evaluating: an integer diff --git a/src/men-sharp-semantics/src/semantics/check/declarations.rs b/src/men-sharp-semantics/src/semantics/check/declarations.rs index b75122d..03a708f 100644 --- a/src/men-sharp-semantics/src/semantics/check/declarations.rs +++ b/src/men-sharp-semantics/src/semantics/check/declarations.rs @@ -27,7 +27,7 @@ impl<'a, 'ast> Checker<'a, 'ast> { } } - fn walk_namespace(&mut self, node: &NamespaceNode<'ast>) { + fn enter_namespace(&mut self, node: &NamespaceNode<'ast>) -> usize { let pushed = node.name.len(); for segment in node.name { @@ -65,12 +65,65 @@ impl<'a, 'ast> Checker<'a, 'ast> { scope.usings = usings; } + pushed + } + + fn walk_namespace(&mut self, node: &NamespaceNode<'ast>) { + let pushed = self.enter_namespace(node); self.walk_nodes(&node.members); let keep = self.scopes.len() - pushed; self.scopes.truncate(keep); } + pub(super) fn bind_constant_nodes( + &mut self, + nodes: &[DeclarationNode<'ast>], + field: SymbolId, + ) -> bool { + for node in nodes { + let found = match node { + DeclarationNode::Namespace(namespace) => { + let pushed = self.enter_namespace(namespace); + let found = self.bind_constant_nodes(&namespace.members, field); + self.scopes.truncate(self.scopes.len() - pushed); + found + } + DeclarationNode::Type(node) => self.bind_constant_type(node, field), + }; + if found { + return true; + } + } + false + } + + fn bind_constant_type(&mut self, node: &TypeNode<'ast>, field: SymbolId) -> bool { + let Some(symbol) = self + .resolver + .declarations + .symbol_of(node.syntax.entity_id()) + else { + return false; + }; + self.type_stack.push(symbol); + let found = if let Some(member) = node.members.iter().find(|member| { + self.resolver + .declarations + .symbol_of(member.syntax.entity_id()) + == Some(field) + }) { + self.check_member(member); + true + } else { + node.nested + .iter() + .any(|nested| self.bind_constant_type(nested, field)) + }; + self.type_stack.pop(); + found + } + fn check_type_declaration(&mut self, node: &TypeNode<'ast>) { let Some(symbol) = self .resolver diff --git a/src/men-sharp-semantics/src/semantics/check/exhaustive.rs b/src/men-sharp-semantics/src/semantics/check/exhaustive.rs index 0aa0014..f6d0035 100644 --- a/src/men-sharp-semantics/src/semantics/check/exhaustive.rs +++ b/src/men-sharp-semantics/src/semantics/check/exhaustive.rs @@ -90,8 +90,13 @@ fn bool_literal(expression: &Expression) -> Option { /// allowed to be. Anything else is `None`: the member is then a case of /// its own, which is right unless it aliases another. pub(super) fn integer_literal_value(expression: &Expression) -> Option { + i64::try_from(wide_integer_literal_value(expression)?).ok() +} + +/// Constant binding also needs unsigned literals above i64::MAX. +pub(super) fn wide_integer_literal_value(expression: &Expression) -> Option { if let Expression::Unary(unary) = expression { - let value = integer_literal_value(unary.operand.as_ref().ok()?)?; + let value = wide_integer_literal_value(unary.operand.as_ref().ok()?)?; return match unary.operator.value { men_sharp_parser::ast::UnaryOperator::Plus => Some(value), men_sharp_parser::ast::UnaryOperator::Minus => value.checked_neg(), @@ -105,7 +110,7 @@ pub(super) fn integer_literal_value(expression: &Expression) -> Option { return None; } if let PrimaryLeft::Parenthesized { expression, .. } = &primary.left { - return integer_literal_value(expression); + return wide_integer_literal_value(expression); } let PrimaryLeft::Literal(LiteralExpression::Integer(text)) = &primary.left else { return None; @@ -116,12 +121,12 @@ pub(super) fn integer_literal_value(expression: &Expression) -> Option { .strip_prefix("0x") .or_else(|| digits.strip_prefix("0X")) { - i64::from_str_radix(hex, 16).ok() + i128::from_str_radix(hex, 16).ok() } else if let Some(bits) = digits .strip_prefix("0b") .or_else(|| digits.strip_prefix("0B")) { - i64::from_str_radix(bits, 2).ok() + i128::from_str_radix(bits, 2).ok() } else { digits.parse().ok() } diff --git a/src/men-sharp-semantics/src/semantics/check/statements.rs b/src/men-sharp-semantics/src/semantics/check/statements.rs index 83ac3f0..7483033 100644 --- a/src/men-sharp-semantics/src/semantics/check/statements.rs +++ b/src/men-sharp-semantics/src/semantics/check/statements.rs @@ -365,7 +365,9 @@ impl<'a, 'ast> Checker<'a, 'ast> { let constant = if declaration.const_keyword.is_some() { match &declarator.initializer { - Some(InitializerValue::Expression(value)) => self.integer_constant(value), + Some(InitializerValue::Expression(value)) => { + self.constant_value_inner(value, self.constant_depth) + } _ => None, } } else {