Skip to content

Commit

Permalink
Check types of LHS and RHS in assignment "to identifier" (#187)
Browse files Browse the repository at this point in the history
Before there was no checking that the types of the LHS and RHS were valid in assignment, with the
exception of checking constness.

This commit introduces some checking in the case that the LHS is represented by an identifier
(i.e. not an indexed identifier) Checking if the LHS is an indexed identifier, for example `b[1] =
measure q;` remains to be implemented

* If types have same base array type, but dimensions differ, an `IncompatibleDimensionError` is recorded. This
  is tested, for example, with
```
bit[3] b;
qubit[2] q;
b = measure q;
```

* If the RHS is an integer literal and the LHS is unsigned, then the sign of the literal is examined and
  the RHS is wrapped in `Cast` if the sign is positive. Otherwise `CastError`.

* If the type of the RHS can be cast to that of the left, the RHS is wrapped in `Cast`.

* Otherwise `IncompatibleTypes` is recorded.

Several tests of these new behaviors are included.

A few details of things added:

* Fix erroneous `Type` of integer literal. Before we had UInt even though in the data we stored the sign
  of the literal. The type is now `Int`.
* Introduce semantic errors: `IncompatibleDimensionError` and `CastError`. The latter is for trying to
  cast a negative integer literal to an unsigned type. We could perhaps to do better. Perhaps just make
  this an `IncompatibleTypes` error instead.
  • Loading branch information
jlapeyre committed Mar 23, 2024
1 parent 682d9b6 commit 3396068
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 14 deletions.
2 changes: 1 addition & 1 deletion crates/oq3_semantics/src/asg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ impl IntLiteral {
}

pub fn to_texpr(self) -> TExpr {
TExpr::new(self.to_expr(), Type::UInt(Some(128), IsConst::True))
TExpr::new(self.to_expr(), Type::Int(Some(128), IsConst::True))
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/oq3_semantics/src/semantic_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub enum SemanticErrorKind {
RedeclarationError(String),
ConstIntegerError, // need a better way to organize this kind of type error
IncompatibleTypesError,
IncompatibleDimensionError,
CastError,
MutateConstError,
IncludeNotInGlobalScopeError,
ReturnInGlobalScopeError,
Expand Down
45 changes: 38 additions & 7 deletions crates/oq3_semantics/src/syntax_to_semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,27 +996,58 @@ fn from_assignment_stmt(
context: &mut Context,
) -> Option<asg::Stmt> {
let nameb = assignment_stmt.identifier(); // LHS of assignment
// LHS is an identifier
if nameb.is_some() {
let name = nameb.as_ref().unwrap();
let name_str = name.string();
let expr = from_expr(assignment_stmt.rhs(), context); // rhs of `=` operator
let mut expr = from_expr(assignment_stmt.rhs(), context).unwrap(); // rhs of `=` operator

let (symbol_id, typ) = context.lookup_symbol(name_str.as_str(), name).as_tuple();
let is_mutating_const = symbol_id.is_ok() && typ.is_const();
let (symbol_id, symbol_type) = context.lookup_symbol(name_str.as_str(), name).as_tuple();
let symbol_ok = symbol_id.is_ok();
let is_mutating_const = symbol_ok && symbol_type.is_const();
let lvalue = asg::LValue::Identifier(symbol_id);
let stmt_asg = Some(asg::Assignment::new(lvalue, expr.unwrap()).to_stmt());
let expr_type = expr.get_type();
// Check that types match, but only if lhs has been declared, in which case
// recording a type error would be redundant.
if symbol_ok && expr_type != &symbol_type {
if expr_type.equal_up_to_dims(&symbol_type) {
context.insert_error(IncompatibleDimensionError, assignment_stmt);
} else if let asg::Expr::Literal(asg::Literal::Int(intlit)) = expr.expression() {
if matches!(symbol_type, Type::UInt(..)) {
if *intlit.sign() {
// FIXME: We are casting to unsigned if the int literal is positive.
// But we are not checking the width.
expr = asg::Cast::new(expr, symbol_type).to_texpr()
} else {
// We call this cast error. Not a great name
// In Rust, `let x: u32 = -1;` gives "cannot apply unary operator `-` to type `u32`"
// But we have combined `-` with the literal already during parsing (I think?).
// In Julia, `x::UInt = -1` throws `InexactError`.
context.insert_error(CastError, assignment_stmt);
}
}
} else {
let promoted_type = types::promote_types(&symbol_type, expr_type);
if promoted_type == symbol_type {
expr = asg::Cast::new(expr, promoted_type.clone()).to_texpr()
} else {
context.insert_error(IncompatibleTypesError, assignment_stmt);
}
}
}
let stmt_asg = Some(asg::Assignment::new(lvalue, expr).to_stmt());
if is_mutating_const {
context.insert_error(MutateConstError, assignment_stmt);
}
return stmt_asg;
}
// LHS is *not* an identifier, rather an indexed identifier
let indexed_identifier_ast = assignment_stmt.indexed_identifier();
let (indexed_identifier, _typ) =
ast_indexed_identifier(&indexed_identifier_ast.unwrap(), context);
let expr = from_expr(assignment_stmt.rhs(), context); // rhs of `=` operator
// let is_mutating_const = symbol_id.is_ok() && typ.is_const();
let expr = from_expr(assignment_stmt.rhs(), context).unwrap(); // rhs of `=` operator
let lvalue = asg::LValue::IndexedIdentifier(indexed_identifier);
Some(asg::Assignment::new(lvalue, expr.unwrap()).to_stmt())
Some(asg::Assignment::new(lvalue, expr).to_stmt())
}

//
Expand Down
36 changes: 31 additions & 5 deletions crates/oq3_semantics/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ impl Type {
_ => None,
}
}

pub fn equal_up_to_dims(&self, other: &Type) -> bool {
use Type::*;
if self == other {
return true;
}
if matches!(self, BitArray(_, _)) && matches!(other, BitArray(_, _)) {
return true;
}
false
}
}

#[test]
Expand Down Expand Up @@ -221,21 +232,36 @@ fn promote_width(ty1: &Type, ty2: &Type) -> Width {

// promotion suitable for some binary operations, eg +, -, *
pub fn promote_types(ty1: &Type, ty2: &Type) -> Type {
use Type::*;
if ty1 == ty2 {
return ty1.clone();
}
let typ = promote_types_symmetric(ty1, ty2);
if typ != Type::Void {
return typ;
}
let typ = promote_types_asymmetric(ty1, ty2);
if typ == Type::Void {
return promote_types_asymmetric(ty2, ty1);
}
typ
}

fn promote_types_symmetric(ty1: &Type, ty2: &Type) -> Type {
use Type::*;
let isconst = promote_constness(ty1, ty2);
match (ty1, ty2) {
// pairs without float
(Int(..), Int(..)) => Int(promote_width(ty1, ty2), isconst),
(UInt(..), UInt(..)) => UInt(promote_width(ty1, ty2), isconst),
(Float(..), Float(..)) => Float(promote_width(ty1, ty2), isconst),
_ => Void,
}
}

// pairs with float
fn promote_types_asymmetric(ty1: &Type, ty2: &Type) -> Type {
use Type::*;
match (ty1, ty2) {
(Int(..), Float(..)) => ty2.clone(),
(Float(..), Int(..)) => ty1.clone(),
(UInt(..), Float(..)) => ty2.clone(),
(Float(..), UInt(..)) => ty1.clone(),
_ => Void,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oq3_semantics/tests/ast_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn test_texpr_int_literal() {
let literal = IntLiteral::new(1_u32, true);
let texpr = literal.clone().to_texpr();
assert_eq!(texpr.expression(), &literal.to_expr());
assert_eq!(texpr.get_type(), &Type::UInt(Some(128), IsConst::True));
assert_eq!(texpr.get_type(), &Type::Int(Some(128), IsConst::True));
}

//
Expand Down
104 changes: 104 additions & 0 deletions crates/oq3_semantics/tests/from_string_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,107 @@ include "stdgates.inc";
assert_eq!(errors.len(), 1);
assert_eq!(program.len(), 1);
}

#[test]
fn test_from_string_check_assign_types_1() {
let code = r##"
bit[3] b;
qubit[2] q;
b = measure q;
"##;
let (program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
assert!(matches!(
&errors[0].kind(),
SemanticErrorKind::IncompatibleDimensionError
));
assert_eq!(program.len(), 3);
}

#[test]
fn test_from_string_check_assign_types_2() {
let code = r##"
float b;
qubit[2] q;
b = measure q;
"##;
let (program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
assert!(matches!(
&errors[0].kind(),
SemanticErrorKind::IncompatibleTypesError
));
assert_eq!(program.len(), 3);
}

// Bug
#[test]
fn test_from_string_check_assign_types_3() {
let code = r##"
float b[2];
qubit[2] q;
b[1] = measure q;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 0);
}

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

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

#[test]
fn test_from_string_check_assign_types_6() {
let code = r##"
uint x0;
x0 = -1;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
assert!(matches!(&errors[0].kind(), SemanticErrorKind::CastError));
}

#[test]
fn test_from_string_check_assign_types_7() {
let code = r##"
int x0;
x0 = 2.0;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
assert!(matches!(
&errors[0].kind(),
SemanticErrorKind::IncompatibleTypesError
));
}

#[test]
fn test_from_string_check_assign_types_8() {
let code = r##"
float xx = 3;
int yy;
yy = xx;
"##;
let (_program, errors, _symbol_table) = parse_string(code);
assert_eq!(errors.len(), 1);
assert!(matches!(
&errors[0].kind(),
SemanticErrorKind::IncompatibleTypesError
));
}

0 comments on commit 3396068

Please sign in to comment.