diff --git a/Cargo.lock b/Cargo.lock index 23eb8c71..cb753dda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,7 @@ dependencies = [ name = "aiken-lang" version = "1.0.20-alpha" dependencies = [ + "blst", "chumsky", "hex", "indexmap", diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index e6b29b41..e84cca8d 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -27,6 +27,7 @@ vec1 = "1.10.1" uplc = { path = '../uplc', version = "1.0.20-alpha" } num-bigint = "0.4.3" petgraph = "0.6.3" +blst = "0.3.11" [target.'cfg(not(target_family="wasm"))'.dependencies] chumsky = "0.9.2" diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index e820de51..fa4226c0 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -11,6 +11,7 @@ use std::{ ops::Range, rc::Rc, }; +use uplc::machine::runtime::Compressable; use vec1::Vec1; pub const CAPTURE_VARIABLE: &str = "_capture"; @@ -492,6 +493,12 @@ pub enum Constant { bytes: Vec, preferred_format: ByteArrayFormatPreference, }, + + CurvePoint { + location: Span, + point: Curve, + preferred_format: ByteArrayFormatPreference, + }, } impl Constant { @@ -500,6 +507,10 @@ impl Constant { Constant::Int { .. } => builtins::int(), Constant::String { .. } => builtins::string(), Constant::ByteArray { .. } => builtins::byte_array(), + Constant::CurvePoint { point, .. } => match point { + Curve::Bls12_381(Bls12_381Point::G1(_)) => builtins::g1_element(), + Curve::Bls12_381(Bls12_381Point::G2(_)) => builtins::g2_element(), + }, } } @@ -507,7 +518,8 @@ impl Constant { match self { Constant::Int { location, .. } | Constant::String { location, .. } - | Constant::ByteArray { location, .. } => *location, + | Constant::ByteArray { location, .. } + | Constant::CurvePoint { location, .. } => *location, } } } @@ -997,6 +1009,78 @@ pub enum ByteArrayFormatPreference { Utf8String, } +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum CurveType { + Bls12_381(Bls12_381PointType), +} + +impl Display for CurveType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CurveType::Bls12_381(point) => write!(f, ""), + } + } +} +impl From<&Curve> for CurveType { + fn from(value: &Curve) -> Self { + match value { + Curve::Bls12_381(point) => CurveType::Bls12_381(point.into()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum Bls12_381PointType { + G1, + G2, +} + +impl From<&Bls12_381Point> for Bls12_381PointType { + fn from(value: &Bls12_381Point) -> Self { + match value { + Bls12_381Point::G1(_) => Bls12_381PointType::G1, + Bls12_381Point::G2(_) => Bls12_381PointType::G2, + } + } +} + +impl Display for Bls12_381PointType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Bls12_381PointType::G1 => write!(f, "G1"), + Bls12_381PointType::G2 => write!(f, "G2"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum Curve { + Bls12_381(Bls12_381Point), +} + +impl Curve { + pub fn compress(&self) -> Vec { + match self { + Curve::Bls12_381(point) => match point { + Bls12_381Point::G1(g1) => g1.compress(), + Bls12_381Point::G2(g2) => g2.compress(), + }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum Bls12_381Point { + G1(blst::blst_p1), + G2(blst::blst_p2), +} + +impl Default for Bls12_381Point { + fn default() -> Self { + Bls12_381Point::G1(Default::default()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum AssignmentKind { Let, diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 4570909e..476659a1 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -4,7 +4,7 @@ use vec1::Vec1; use crate::{ ast::{ - self, Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, + self, Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Curve, DefinitionLocation, IfBranch, Located, LogicalOpChainKind, ParsedCallArg, Pattern, RecordUpdateSpread, Span, TraceKind, TypedClause, TypedRecordUpdateArg, UnOp, UntypedClause, UntypedRecordUpdateArg, @@ -34,6 +34,12 @@ pub enum TypedExpr { bytes: Vec, }, + CurvePoint { + location: Span, + tipo: Rc, + point: Curve, + }, + Sequence { location: Span, expressions: Vec, @@ -186,7 +192,8 @@ impl TypedExpr { | Self::Assignment { tipo, .. } | Self::ModuleSelect { tipo, .. } | Self::RecordAccess { tipo, .. } - | Self::RecordUpdate { tipo, .. } => tipo.clone(), + | Self::RecordUpdate { tipo, .. } + | Self::CurvePoint { tipo, .. } => tipo.clone(), Self::Pipeline { expressions, .. } | Self::Sequence { expressions, .. } => { expressions.last().map(TypedExpr::tipo).unwrap_or_else(void) } @@ -227,7 +234,8 @@ impl TypedExpr { | TypedExpr::ByteArray { .. } | TypedExpr::Assignment { .. } | TypedExpr::TupleIndex { .. } - | TypedExpr::RecordAccess { .. } => None, + | TypedExpr::RecordAccess { .. } + | TypedExpr::CurvePoint { .. } => None, TypedExpr::If { .. } => None, // TODO: test @@ -269,7 +277,8 @@ impl TypedExpr { | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } - | Self::RecordUpdate { location, .. } => *location, + | Self::RecordUpdate { location, .. } + | Self::CurvePoint { location, .. } => *location, Self::If { branches, .. } => branches.first().body.type_defining_location(), @@ -306,7 +315,8 @@ impl TypedExpr { | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } - | Self::RecordUpdate { location, .. } => *location, + | Self::RecordUpdate { location, .. } + | Self::CurvePoint { location, .. } => *location, } } @@ -323,7 +333,8 @@ impl TypedExpr { | TypedExpr::UInt { .. } | TypedExpr::String { .. } | TypedExpr::ByteArray { .. } - | TypedExpr::ModuleSelect { .. } => Some(Located::Expression(self)), + | TypedExpr::ModuleSelect { .. } + | TypedExpr::CurvePoint { .. } => Some(Located::Expression(self)), TypedExpr::Trace { text, then, .. } => text .find_node(byte_index) @@ -472,6 +483,12 @@ pub enum UntypedExpr { preferred_format: ByteArrayFormatPreference, }, + CurvePoint { + location: Span, + point: Curve, + preferred_format: ByteArrayFormatPreference, + }, + PipeLine { expressions: Vec1, one_liner: bool, @@ -733,7 +750,8 @@ impl UntypedExpr { | Self::RecordUpdate { location, .. } | Self::UnOp { location, .. } | Self::LogicalOpChain { location, .. } - | Self::If { location, .. } => *location, + | Self::If { location, .. } + | Self::CurvePoint { location, .. } => *location, Self::Sequence { location, expressions, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 76fd84ec..1e667443 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1,11 +1,11 @@ use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, - ClauseGuard, Constant, DataType, Definition, Function, IfBranch, LogicalOpChainKind, - ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, - TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport, UntypedArg, UntypedClause, - UntypedClauseGuard, UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern, - UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE, + ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, + LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, + RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport, + UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedFunction, + UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, CAPTURE_VARIABLE, }, docvec, expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, @@ -352,7 +352,12 @@ impl<'comments> Formatter<'comments> { bytes, preferred_format, .. - } => self.bytearray(bytes, preferred_format), + } => self.bytearray(bytes, None, preferred_format), + Constant::CurvePoint { + point, + preferred_format, + .. + } => self.bytearray(&point.compress(), Some(point.into()), preferred_format), Constant::Int { value, base, .. } => self.int(value, base), Constant::String { value, .. } => self.string(value), } @@ -676,17 +681,24 @@ impl<'comments> Formatter<'comments> { pub fn bytearray<'a>( &mut self, - bytes: &'a [u8], + bytes: &[u8], + curve: Option, preferred_format: &ByteArrayFormatPreference, ) -> Document<'a> { match preferred_format { ByteArrayFormatPreference::HexadecimalString => "#" .to_doc() + .append(Document::String( + curve.map(|c| c.to_string()).unwrap_or_default(), + )) .append("\"") .append(Document::String(hex::encode(bytes))) .append("\""), ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal { .. }) => "#" .to_doc() + .append(Document::String( + curve.map(|c| c.to_string()).unwrap_or_default(), + )) .append( flex_break("[", "[") .append(join(bytes.iter().map(|b| b.to_doc()), break_(",", ", "))) @@ -697,6 +709,9 @@ impl<'comments> Formatter<'comments> { .group(), ByteArrayFormatPreference::ArrayOfBytes(Base::Hexadecimal) => "#" .to_doc() + .append(Document::String( + curve.map(|c| c.to_string()).unwrap_or_default(), + )) .append( flex_break("[", "[") .append(join( @@ -771,7 +786,13 @@ impl<'comments> Formatter<'comments> { bytes, preferred_format, .. - } => self.bytearray(bytes, preferred_format), + } => self.bytearray(bytes, None, preferred_format), + + UntypedExpr::CurvePoint { + point, + preferred_format, + .. + } => self.bytearray(&point.compress(), Some(point.into()), preferred_format), UntypedExpr::If { branches, diff --git a/crates/aiken-lang/src/parser/definition/constant.rs b/crates/aiken-lang/src/parser/definition/constant.rs index 97292038..01cddaec 100644 --- a/crates/aiken-lang/src/parser/definition/constant.rs +++ b/crates/aiken-lang/src/parser/definition/constant.rs @@ -1,4 +1,5 @@ use chumsky::prelude::*; +use uplc::machine::runtime::Compressable; use crate::{ ast, @@ -45,14 +46,37 @@ pub fn value() -> impl Parser { base, }); - let constant_bytearray_parser = - literal::bytearray( - |bytes, preferred_format, location| ast::Constant::ByteArray { + let constant_bytearray_parser = literal::bytearray( + |bytes, preferred_format, curve, location, emit| match curve { + Some(curve @ ast::CurveType::Bls12_381(point)) => { + let point = match point { + ast::Bls12_381PointType::G1 => { + blst::blst_p1::uncompress(&bytes).map(ast::Bls12_381Point::G1) + } + ast::Bls12_381PointType::G2 => { + blst::blst_p2::uncompress(&bytes).map(ast::Bls12_381Point::G2) + } + }; + + let point = point.unwrap_or_else(|_err| { + emit(ParseError::point_not_on_curve(curve, location)); + + ast::Bls12_381Point::default() + }); + + ast::Constant::CurvePoint { + location, + point: ast::Curve::Bls12_381(point), + preferred_format, + } + } + None => ast::Constant::ByteArray { location, bytes, preferred_format, }, - ); + }, + ); choice(( constant_string_parser, diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index aeacaba3..01be9ca2 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -1,4 +1,7 @@ -use crate::{ast::Span, parser::token::Token}; +use crate::{ + ast::{CurveType, Span}, + parser::token::Token, +}; use indoc::formatdoc; use miette::Diagnostic; use owo_colors::{OwoColorize, Stream::Stdout}; @@ -46,6 +49,32 @@ impl ParseError { } } + pub fn point_not_on_curve(curve: CurveType, span: Span) -> Self { + Self { + kind: ErrorKind::PointNotOnCurve { curve }, + span, + while_parsing: None, + expected: HashSet::new(), + label: Some("out off curve"), + } + } + + pub fn unknown_point_curve(curve: String, point: Option, span: Span) -> Self { + let label = if point.is_some() { + Some("unknown curve") + } else { + Some("unknown point") + }; + + Self { + kind: ErrorKind::UnknownCurvePoint { curve, point }, + span, + while_parsing: None, + expected: HashSet::new(), + label, + } + } + pub fn malformed_base16_string_literal(span: Span) -> Self { Self { kind: ErrorKind::MalformedBase16StringLiteral, @@ -134,6 +163,15 @@ pub enum ErrorKind { hint: Option, }, + #[error("I tripped over a {}", fmt_curve_type(.curve))] + PointNotOnCurve { curve: CurveType }, + + #[error("I tripped over a {}", fmt_unknown_curve(.curve, .point))] + UnknownCurvePoint { + curve: String, + point: Option, + }, + #[error("I tripped over a malformed hexadecimal digits.")] #[diagnostic(help("{}", formatdoc! { r#"When numbers starts with '0x', they are treated as hexadecimal numbers. Thus, only digits from 0-9 or letter from a-f (or A-F) can be used following a '0x' number declaration. Plus, hexadecimal digits always go by pairs, so the total number of digits must be even (not counting leading zeros)."# @@ -186,6 +224,32 @@ pub enum ErrorKind { InvalidWhenClause, } +fn fmt_curve_type(curve: &CurveType) -> String { + match curve { + CurveType::Bls12_381(point) => { + format!("{point} point that is not in the bls12_381 curve") + } + } +} + +fn fmt_unknown_curve(curve: &String, point: &Option) -> String { + match point { + Some(point) => { + format!( + "{} which is an unknown point for curve {}", + point.if_supports_color(Stdout, |s| s.purple()), + curve.if_supports_color(Stdout, |s| s.purple()), + ) + } + None => { + format!( + "{} which is an unknown curve", + curve.if_supports_color(Stdout, |s| s.purple()) + ) + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] pub enum Pattern { #[error("I found an unexpected char '{0:?}'.")] diff --git a/crates/aiken-lang/src/parser/expr/bytearray.rs b/crates/aiken-lang/src/parser/expr/bytearray.rs index 62945b9b..4aefefae 100644 --- a/crates/aiken-lang/src/parser/expr/bytearray.rs +++ b/crates/aiken-lang/src/parser/expr/bytearray.rs @@ -1,13 +1,43 @@ use chumsky::prelude::*; +use uplc::machine::runtime::Compressable; -use crate::parser::{error::ParseError, expr::UntypedExpr, literal::bytearray, token::Token}; +use crate::{ + ast, + parser::{error::ParseError, expr::UntypedExpr, literal::bytearray, token::Token}, +}; pub fn parser() -> impl Parser { - bytearray(|bytes, preferred_format, location| UntypedExpr::ByteArray { - location, - bytes, - preferred_format, - }) + bytearray( + |bytes, preferred_format, curve, location, emit| match curve { + Some(curve @ ast::CurveType::Bls12_381(point)) => { + let point = match point { + ast::Bls12_381PointType::G1 => { + blst::blst_p1::uncompress(&bytes).map(ast::Bls12_381Point::G1) + } + ast::Bls12_381PointType::G2 => { + blst::blst_p2::uncompress(&bytes).map(ast::Bls12_381Point::G2) + } + }; + + let point = point.unwrap_or_else(|_err| { + emit(ParseError::point_not_on_curve(curve, location)); + + ast::Bls12_381Point::default() + }); + + UntypedExpr::CurvePoint { + location, + point: ast::Curve::Bls12_381(point), + preferred_format, + } + } + None => UntypedExpr::ByteArray { + location, + bytes, + preferred_format, + }, + }, + ) } #[cfg(test)] diff --git a/crates/aiken-lang/src/parser/literal/bytearray.rs b/crates/aiken-lang/src/parser/literal/bytearray.rs index e48c49e8..67349b07 100644 --- a/crates/aiken-lang/src/parser/literal/bytearray.rs +++ b/crates/aiken-lang/src/parser/literal/bytearray.rs @@ -9,16 +9,70 @@ use crate::{ }; pub fn parser( - into: impl Fn(Vec, ast::ByteArrayFormatPreference, ast::Span) -> A, + into: impl Fn( + Vec, + ast::ByteArrayFormatPreference, + Option, + ast::Span, + &mut dyn FnMut(ParseError), + ) -> A, ) -> impl Parser { - choice((array_of_bytes(), hex_string(), utf8_string())) - .map_with_span(move |(preferred_format, bytes), span| into(bytes, preferred_format, span)) + choice(( + array_of_bytes(), + hex_string(), + utf8_string().map(|(p, b)| (None, p, b)), + )) + .validate(move |(curve, preferred_format, bytes), span, emit| { + into(bytes, preferred_format, curve, span, emit) + }) } -pub fn array_of_bytes( -) -> impl Parser), Error = ParseError> { +fn curve_point() -> impl Parser { + just(Token::Less) + .ignore_then(select! {Token::UpName {name} => name}) + .then_ignore(just(Token::Comma)) + .then(select! {Token::UpName {name} => name}) + .then_ignore(just(Token::Greater)) + .validate( + |(curve_type, point_type), span, emit| match curve_type.as_str() { + "Bls12_381" => { + let point = match point_type.as_str() { + "G1" => ast::Bls12_381PointType::G1, + "G2" => ast::Bls12_381PointType::G2, + _ => { + emit(ParseError::unknown_point_curve( + curve_type, + Some(point_type), + span, + )); + + ast::Bls12_381PointType::G1 + } + }; + + ast::CurveType::Bls12_381(point) + } + _ => { + emit(ParseError::unknown_point_curve(curve_type, None, span)); + + ast::CurveType::Bls12_381(ast::Bls12_381PointType::G1) + } + }, + ) +} + +pub fn array_of_bytes() -> impl Parser< + Token, + ( + Option, + ast::ByteArrayFormatPreference, + Vec, + ), + Error = ParseError, +> { just(Token::Hash) - .ignore_then( + .ignore_then(curve_point().or_not()) + .then( select! {Token::Int {value, base, ..} => (value, base)} .validate(|(value, base), span, emit| { let byte: u8 = match value.parse() { @@ -38,7 +92,7 @@ pub fn array_of_bytes( .allow_trailing() .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)), ) - .validate(|bytes, span, emit| { + .validate(|(curve, bytes), span, emit| { let base = bytes.iter().try_fold(None, |acc, (_, base)| match acc { None => Ok(Some(base)), Some(previous_base) if previous_base == base => Ok(Some(base)), @@ -58,15 +112,33 @@ pub fn array_of_bytes( Ok(Some(base)) => *base, }; - (bytes.into_iter().map(|(b, _)| b).collect::>(), base) + ( + curve, + bytes.into_iter().map(|(b, _)| b).collect::>(), + base, + ) + }) + .map(|(curve, bytes, base)| { + ( + curve, + ast::ByteArrayFormatPreference::ArrayOfBytes(base), + bytes, + ) }) - .map(|(bytes, base)| (ast::ByteArrayFormatPreference::ArrayOfBytes(base), bytes)) } -pub fn hex_string( -) -> impl Parser), Error = ParseError> { +pub fn hex_string() -> impl Parser< + Token, + ( + Option, + ast::ByteArrayFormatPreference, + Vec, + ), + Error = ParseError, +> { just(Token::Hash) - .ignore_then( + .ignore_then(curve_point().or_not()) + .then( select! {Token::ByteString {value} => value}.validate(|value, span, emit| { match hex::decode(value) { Ok(bytes) => bytes, @@ -77,7 +149,13 @@ pub fn hex_string( } }), ) - .map(|token| (ast::ByteArrayFormatPreference::HexadecimalString, token)) + .map(|(curve, token)| { + ( + curve, + ast::ByteArrayFormatPreference::HexadecimalString, + token, + ) + }) } pub fn utf8_string( diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index f7a76b79..46c9c9a9 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -3,13 +3,13 @@ use vec1::Vec1; use crate::{ ast::{ - Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, - ClauseGuard, Constant, IfBranch, LogicalOpChainKind, RecordUpdateSpread, Span, TraceKind, - Tracing, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedIfBranch, - TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedClause, UntypedClauseGuard, - UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg, + Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, + CallArg, ClauseGuard, Constant, Curve, IfBranch, LogicalOpChainKind, RecordUpdateSpread, + Span, TraceKind, Tracing, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, + TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedClause, + UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg, }, - builtins::{bool, byte_array, function, int, list, string, tuple}, + builtins::{bool, byte_array, function, g1_element, g2_element, int, list, string, tuple}, expr::{FnStyle, TypedExpr, UntypedExpr}, format, tipo::fields::FieldMap, @@ -321,6 +321,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, } => self.infer_bytearray(bytes, preferred_format, location), + UntypedExpr::CurvePoint { + location, point, .. + } => self.infer_curve_point(point, location), + UntypedExpr::RecordUpdate { location, constructor, @@ -363,6 +367,21 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + fn infer_curve_point(&mut self, curve: Curve, location: Span) -> Result { + let tipo = match curve { + Curve::Bls12_381(point) => match point { + Bls12_381Point::G1(_) => g1_element(), + Bls12_381Point::G2(_) => g2_element(), + }, + }; + + Ok(TypedExpr::CurvePoint { + location, + point: curve, + tipo, + }) + } + fn infer_trace_if_false( &mut self, value: UntypedExpr, @@ -1345,6 +1364,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { preferred_format, }) } + Constant::CurvePoint { + location, + point, + preferred_format, + } => Ok(Constant::CurvePoint { + location, + point, + preferred_format, + }), }?; // Check type annotation is accurate. @@ -2000,7 +2028,8 @@ fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> { | UntypedExpr::Var { .. } | UntypedExpr::LogicalOpChain { .. } | UntypedExpr::TraceIfFalse { .. } - | UntypedExpr::When { .. } => Ok(()), + | UntypedExpr::When { .. } + | UntypedExpr::CurvePoint { .. } => Ok(()), } } fn assert_assignment(expr: &UntypedExpr) -> Result<(), Error> {