Skip to content

Commit

Permalink
Fix several bugs in type checking and explicit and implicit casting
Browse files Browse the repository at this point in the history
These are mostly in classical declaration statements.
Several tests have been added.

Closes #208
  • Loading branch information
jlapeyre committed Mar 29, 2024
1 parent 59cc778 commit e81aa88
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 4 deletions.
6 changes: 5 additions & 1 deletion crates/oq3_parser/src/grammar/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,11 @@ fn expr_bp(
) -> Option<(CompletedMarker, BlockLike)> {
let m = m.unwrap_or_else(|| p.start());
#[allow(clippy::nonminimal_bool)]
if !p.at_ts(EXPR_FIRST) && !(p.current().is_classical_type() && p.nth(1) == T!['(']) {
// The second clause here is for cast expressions (only cast expressions?), such as
// int(expr) and int[32](expr).
if !p.at_ts(EXPR_FIRST)
&& !(p.current().is_classical_type() && matches!(p.nth(1), T!['('] | T!['[']))
{
p.err_recover("expr_bp: expected expression", atom::EXPR_RECOVERY_SET); // FIXME, remove debug from string
m.abandon(p);
return None;
Expand Down
7 changes: 6 additions & 1 deletion crates/oq3_semantics/src/asg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,12 @@ impl BitStringLiteral {
}

pub fn to_texpr(self) -> TExpr {
let width: usize = self.value.len();
// Don't count underscores when counting bits.
let width: usize = self
.value
.chars()
.filter(|c| *c == '0' || *c == '1')
.count();
TExpr::new(
self.to_expr(),
Type::BitArray(ArrayDims::D1(width), IsConst::True),
Expand Down
22 changes: 22 additions & 0 deletions crates/oq3_semantics/src/syntax_to_semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,16 @@ fn from_scalar_type(
}
}

// Return `true` if the literal
fn can_cast_literal(lhs_type: &Type, init_type: &Type, literal: &asg::Literal) -> bool {
if matches!(lhs_type, &Type::UInt(..)) {
if let asg::Literal::Int(intlit) = literal {
return *intlit.sign();
}
}
types::can_cast_literal(lhs_type, init_type)
}

fn from_classical_declaration_statement(
type_decl: &synast::ClassicalDeclarationStatement,
context: &mut Context,
Expand All @@ -1020,6 +1030,18 @@ fn from_classical_declaration_statement(
if types::equal_up_to_constness(&lhs_type, init_type) {
return asg::DeclareClassical::new(symbol_id, Some(initializer)).to_stmt();
}
// From this point, we need to cast, if possible.
// So, we either cast, or record an error saying types are incompatible.
if let asg::Expr::Literal(literal) = initializer.expression() {
if can_cast_literal(&lhs_type, init_type, literal) {
let new_initializer =
asg::Cast::new(initializer.clone(), lhs_type.clone()).to_texpr();
return asg::DeclareClassical::new(symbol_id, Some(new_initializer)).to_stmt();
} else {
context.insert_error(IncompatibleTypesError, type_decl);
return asg::DeclareClassical::new(symbol_id, Some(initializer)).to_stmt();
}
}
let promoted_type = types::promote_types_not_equal(&lhs_type, init_type);
let new_initializer = if types::equal_up_to_constness(&promoted_type, &lhs_type) {
// Very often `promoted_type` is correct. But it may be off by constness.
Expand Down
59 changes: 59 additions & 0 deletions crates/oq3_semantics/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub enum Type {
// property is allowed to differ.
pub(crate) fn equal_up_to_constness(ty1: &Type, ty2: &Type) -> bool {
use Type::*;
// FIXME: Make sure we can remove following. Looks inefficient
if ty1 == ty2 {
return true;
}
Expand All @@ -97,12 +98,33 @@ pub(crate) fn equal_up_to_constness(ty1: &Type, ty2: &Type) -> bool {
(Int(w1, _), Int(w2, _)) => w1 == w2,
(UInt(w1, _), UInt(w2, _)) => w1 == w2,
(Float(w1, _), Float(w2, _)) => w1 == w2,
(Complex(w1, _), Complex(w2, _)) => w1 == w2,
(Angle(w1, _), Angle(w2, _)) => w1 == w2,
(BitArray(dims1, _), BitArray(dims2, _)) => dims1 == dims2,
_ => false,
}
}

// Are the base types of the scalars equal?
// (That is modulo width anc constness?)
// Returns `false` for all array types. Not sure what
// we need from arrays.
fn equal_base_type(ty1: &Type, ty2: &Type) -> bool {
use Type::*;
matches!(
(ty1, ty2),
(Bit(_), Bit(_))
| (Duration(_), Duration(_))
| (Bool(_), Bool(_))
| (Stretch(_), Stretch(_))
| (Int(..), Int(..))
| (UInt(..), UInt(..))
| (Float(..), Float(..))
| (Complex(..), Complex(..))
| (Angle(..), Angle(..))
)
}

// OQ3 supports arrays with number of dims up to seven.
// Probably exists a much better way to represent dims... [usize, N]
// Could use Box for higher dimensional arrays, or...
Expand Down Expand Up @@ -268,10 +290,14 @@ fn test_type_enum2() {
// Promotion
//

// `const` is less than non-`const`.
// (why does this look like the opposite of correct definition?)
fn promote_constness(ty1: &Type, ty2: &Type) -> IsConst {
IsConst::from(ty1.is_const() && ty2.is_const())
}

// Return greater of the `Width`s of the types.
// The width `None` is the greatest width.
fn promote_width(ty1: &Type, ty2: &Type) -> Width {
match (ty1.width(), ty2.width()) {
(Some(width1), Some(width2)) => Some(std::cmp::max(width1, width2)),
Expand Down Expand Up @@ -323,6 +349,39 @@ fn promote_types_asymmetric(ty1: &Type, ty2: &Type) -> Type {
match (ty1, ty2) {
(Int(..), Float(..)) => ty2.clone(),
(UInt(..), Float(..)) => ty2.clone(),
(Float(..), Complex(..)) => ty2.clone(),
_ => Void,
}
}

/// Can the literal type be cast to type `ty1` for assignment?
/// The width of a literal is a fiction that is not in the spec.
/// So when casting, the width does not matter.
///
/// We currently have `128` for the width of a literal `Int`.
/// and the literal parsed into a Rust `u128` plus a bool for
/// a sign. We need to check, outside of the semantics whether
/// the value of type `u128` can be cast to the lhs type.
/// For that, we need the value. `can_cast_literal` does not
/// know the value.
pub fn can_cast_literal(ty1: &Type, ty_lit: &Type) -> bool {
use Type::*;
if equal_base_type(ty1, ty_lit) {
return true;
}
match (ty1, ty_lit) {
(Float(..), Int(..)) => true,
(Float(..), UInt(..)) => true,
(Complex(..), Float(..)) => true,
(Complex(..), Int(..)) => true,
(Complex(..), UInt(..)) => true,

// Listing these explicitly is slower, but
// might be better for maintaining and debugging.
(Int(..), Float(..)) => false,
(UInt(..), Float(..)) => false,
(Int(..), Complex(..)) => false,
(UInt(..), Complex(..)) => false,
_ => false,
}
}
105 changes: 105 additions & 0 deletions crates/oq3_semantics/tests/from_string_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,3 +997,108 @@ duration a = 2.0;
SemanticErrorKind::IncompatibleTypesError
));
}

// Issue #208
#[test]
fn test_from_string_declaration_type_check_10() {
let code = r##"
complex[float[64]] c1 = 1.0 + 2.0im;
"##;
let (program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
assert_eq!(program.len(), 1);
}

// Issue #208
#[test]
fn test_from_string_declaration_type_check_11() {
let code = r##"
const int[64] i1 = 4;
"##;
let (program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
assert_eq!(program.len(), 1);
}

// Issue #208
#[test]
fn test_from_string_declaration_type_check_12() {
let code = r##"
const bit[8] b2 = "0010_1010";
"##;
let (program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
assert_eq!(program.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_13() {
let code = r##"
float[64] c1 = 1.0 + 2.0im;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_14() {
let code = r##"
float c1 = 1.0 + 2.0im;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_15() {
let code = r##"
int c1 = 1.0 + 2.0im;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_16() {
let code = r##"
int[8] c1 = 1.0 + 2.0im;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_17() {
let code = r##"
uint a = 1;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
}

#[test]
fn test_from_string_declaration_type_check_18() {
let code = r##"
uint[64] a = 22;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
}

#[test]
fn test_from_string_declaration_type_check_19() {
let code = r##"
uint[64] a = -1;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}

#[test]
fn test_from_string_declaration_type_check_20() {
let code = r##"
uint a = -99;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
}
32 changes: 32 additions & 0 deletions crates/oq3_semantics/tests/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ defcal h $0 { }
assert_eq!(program.len(), 0);
}

// 17dedf
#[test]
fn test_spec_types_6() {
let code = r#"
Expand All @@ -172,3 +173,34 @@ bit[8] name = "00001111";
assert!(errors.is_empty());
assert_eq!(program.len(), 2);
}

// 17dedf
#[test]
fn test_spec_types_7() {
let code = r#"
// Declare a 32-bit unsigned integer
uint[32] my_uint = 10;
// Declare a 16 bit signed integer
int[16] my_int;
my_int = int[16](my_uint);
// Declare a machine-sized integer
int my_machine_int;
"#;
let (program, errors, _symbol_table) = parse_string(code);
assert!(errors.is_empty());
assert_eq!(program.len(), 4);
}

// 17dedf
#[test]
fn test_spec_types_8() {
let code = r#"
// Declare a single-precision 32-bit float
float[32] my_float = π;
// Declare a machine-precision float.
float my_machine_float = 2.3;
"#;
let (program, errors, _symbol_table) = parse_string(code);
assert!(errors.is_empty());
assert_eq!(program.len(), 2);
}
4 changes: 2 additions & 2 deletions crates/oq3_syntax/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,12 @@ int(x) + z;
assert_eq!(parse.errors.len(), 0);
}

// FIXME: Should be zero errors!
// Issue #208 and associated PR
#[test]
fn parse_cast_expr_test_7() {
let code = r##"
z + int[32](x);
"##;
let parse = SourceFile::parse(code);
assert_eq!(parse.errors.len(), 2);
assert_eq!(parse.errors.len(), 0);
}

0 comments on commit e81aa88

Please sign in to comment.