diff --git a/src/parser/uniterm.rs b/src/parser/uniterm.rs index bbe2e7bf12..43d54a8763 100644 --- a/src/parser/uniterm.rs +++ b/src/parser/uniterm.rs @@ -286,12 +286,15 @@ impl UniRecord { } } - /// Checks if this record qualifies as a record type. If this function returns true, then - /// `into_type_strict()` must succeed. + /// Checks if this record qualifies as a record type. If this function + /// returns true, then `into_type_strict()` must succeed. pub fn is_record_type(&self) -> bool { self.fields.iter().all(|field_def| { - // Warning: this pattern must stay in sync with the corresponding pattern in `into_type_strict`. - matches!(&field_def.field, + // Field paths with a depth > 1 are not supported in record types. + field_def.path.len() == 1 + // Warning: this pattern must stay in sync with the + // corresponding pattern in `into_type_strict`. + && matches!(&field_def.field, Field { value: None, metadata: @@ -313,9 +316,9 @@ impl UniRecord { }) } - /// a plain record type, uniquely containing fields of the form `fields: Type`. Currently, this - /// doesn't support the field path syntax: `{foo.bar.baz : Type}.into_type_strict()` returns an - /// `Err`. + /// A plain record type, uniquely containing fields of the form `fields: + /// Type`. Currently, this doesn't support the field path syntax: + /// `{foo.bar.baz : Type}.into_type_strict()` returns an `Err`. pub fn into_type_strict(self) -> Result { fn term_to_record_rows( id: Ident, diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 41f088e735..a959a9d21d 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -162,6 +162,8 @@ enum ErrorExpectation { AnyParseError, #[serde(rename = "ParseError::DuplicateIdentInRecordPattern")] ParseDuplicateIdentInRecordPattern { ident: String }, + #[serde(rename = "ParseError::TypedFieldWithoutDefinition")] + ParseTypedFieldWithoutDefinition, #[serde(rename = "ImportError::ParseError")] ImportParseError, } @@ -190,17 +192,24 @@ impl PartialEq for ErrorExpectation { Error::TypecheckError(TypecheckError::MissingDynTail(..)), ) | (TypecheckExtraDynTail, Error::TypecheckError(TypecheckError::ExtraDynTail(..))) - | (ImportParseError, Error::ImportError(ImportError::ParseErrors(..))) - | (AnyParseError, Error::ParseErrors(..)) => true, - (ParseDuplicateIdentInRecordPattern { ident }, Error::ParseErrors(e)) => { - let first_error = e + | (ImportParseError, Error::ImportError(ImportError::ParseErrors(..))) => true, + (e, Error::ParseErrors(es)) => { + let first_error = es .errors .first() .expect("Got ParserErrors without any errors"); - matches!( - first_error, - ParseError::DuplicateIdentInRecordPattern { ident: id1, .. } if ident.as_str() == id1.label() - ) + match (e, first_error) { + (AnyParseError, _) => true, + ( + ParseDuplicateIdentInRecordPattern { ident }, + ParseError::DuplicateIdentInRecordPattern { ident: ident1, .. }, + ) => ident.as_str() == ident1.label(), + ( + ParseTypedFieldWithoutDefinition, + ParseError::TypedFieldWithoutDefinition { .. }, + ) => true, + _ => false, + } } (EvalFieldMissing { field }, Error::EvalError(EvalError::FieldMissing(ident, ..))) => { field == ident @@ -269,6 +278,9 @@ impl std::fmt::Display for ErrorExpectation { ParseDuplicateIdentInRecordPattern { ident } => { format!("ParseError::DuplicateIdentInRecordPattern({ident})") } + ParseTypedFieldWithoutDefinition => { + "ParseError::TypedFieldWithoutDefinition".to_owned() + } ImportParseError => "ImportError::ParseError".to_owned(), EvalBlameError => "EvalError::BlameError".to_owned(), EvalTypeError => "EvalError::TypeError".to_owned(), diff --git a/tests/integration/typecheck/fail/record-typed-field-path.ncl b/tests/integration/typecheck/fail/record-typed-field-path.ncl new file mode 100644 index 0000000000..9800c87d11 --- /dev/null +++ b/tests/integration/typecheck/fail/record-typed-field-path.ncl @@ -0,0 +1,6 @@ +# test.type = 'error' +# eval = 'typecheck' +# +# [test.metadata] +# error = 'ParseError::TypedFieldWithoutDefinition' +{ x.y : Number } \ No newline at end of file