Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
containing scientific notation or trailing zeros (i.e. `100` and `1e2`).
([ptdewey](https://github.com/ptdewey))

- Record update syntax now works with spreading records in type constructors
for const values:
```gleam
const a = Foo(1, 2)
const b = Foo(..a, 3)
```
([Adi Salimgereyev](https://github.com/abs0luty))

### Build tool

- The help text displayed by `gleam dev --help`, `gleam test --help`, and
Expand Down
2 changes: 1 addition & 1 deletion compiler-core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub mod visit;
pub use self::typed::{InvalidExpression, TypedExpr};
pub use self::untyped::{FunctionLiteralKind, UntypedExpr};

pub use self::constant::{Constant, TypedConstant, UntypedConstant};
pub use self::constant::{Constant, ConstantRecordUpdateArg, TypedConstant, UntypedConstant};

use crate::analyse::Inferred;
use crate::bit_array;
Expand Down
59 changes: 59 additions & 0 deletions compiler-core/src/ast/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ pub enum Constant<T, RecordTag> {
record_constructor: Option<Box<ValueConstructor>>,
},

RecordUpdate {
location: SrcSpan,
module: Option<(EcoString, SrcSpan)>,
name: EcoString,
record: Box<Self>,
arguments: Vec<ConstantRecordUpdateArg<Self>>,
tag: RecordTag,
type_: T,
field_map: Option<FieldMap>,
},

BitArray {
location: SrcSpan,
segments: Vec<BitArraySegment<Self, T>>,
Expand Down Expand Up @@ -85,6 +96,7 @@ impl TypedConstant {
}
Constant::List { type_, .. }
| Constant::Record { type_, .. }
| Constant::RecordUpdate { type_, .. }
| Constant::Var { type_, .. }
| Constant::Invalid { type_, .. } => type_.clone(),
}
Expand All @@ -108,6 +120,16 @@ impl TypedConstant {
.iter()
.find_map(|argument| argument.find_node(byte_index))
.unwrap_or(Located::Constant(self)),
Constant::RecordUpdate {
record, arguments, ..
} => record
.find_node(byte_index)
.or_else(|| {
arguments
.iter()
.find_map(|arg| arg.value.find_node(byte_index))
})
.unwrap_or(Located::Constant(self)),
Constant::BitArray { segments, .. } => segments
.iter()
.find_map(|segment| segment.find_node(byte_index))
Expand Down Expand Up @@ -139,6 +161,7 @@ impl TypedConstant {
} => value_constructor
.as_ref()
.map(|constructor| constructor.definition_location()),
Constant::RecordUpdate { .. } => None,
}
}

Expand All @@ -161,6 +184,17 @@ impl TypedConstant {
.map(|argument| argument.value.referenced_variables())
.fold(im::hashset![], im::HashSet::union),

Constant::RecordUpdate {
record, arguments, ..
} => {
let record_vars = record.referenced_variables();
let arg_vars = arguments
.iter()
.map(|arg| arg.value.referenced_variables())
.fold(im::hashset![], im::HashSet::union);
record_vars.union(arg_vars)
}

Constant::BitArray { segments, .. } => segments
.iter()
.map(|segment| {
Expand Down Expand Up @@ -194,6 +228,7 @@ impl<A, B> Constant<A, B> {
| Constant::Tuple { location, .. }
| Constant::String { location, .. }
| Constant::Record { location, .. }
| Constant::RecordUpdate { location, .. }
| Constant::BitArray { location, .. }
| Constant::Var { location, .. }
| Constant::Invalid { location, .. }
Expand All @@ -212,6 +247,7 @@ impl<A, B> Constant<A, B> {
Constant::Tuple { .. }
| Constant::List { .. }
| Constant::Record { .. }
| Constant::RecordUpdate { .. }
| Constant::BitArray { .. }
| Constant::StringConcatenation { .. }
| Constant::Invalid { .. } => false,
Expand All @@ -234,3 +270,26 @@ impl<A, B> bit_array::GetLiteralValue for Constant<A, B> {
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstantRecordUpdateArg<Constant> {
pub label: EcoString,
pub location: SrcSpan,
pub value: Constant,
}

impl<Constant> ConstantRecordUpdateArg<Constant> {
#[must_use]
pub fn uses_label_shorthand(&self) -> bool
where
Constant: HasLocation,
{
self.value.location() == self.location
}
}

impl<Constant> HasLocation for ConstantRecordUpdateArg<Constant> {
fn location(&self) -> SrcSpan {
self.location
}
}
80 changes: 71 additions & 9 deletions compiler-core/src/ast_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ use vec1::Vec1;
use crate::{
analyse::Inferred,
ast::{
Assert, AssignName, Assignment, BinOp, BitArraySize, CallArg, Constant, Definition,
FunctionLiteralKind, Pattern, RecordBeingUpdated, SrcSpan, Statement, TailPattern,
TargetedDefinition, TodoKind, TypeAst, TypeAstConstructor, TypeAstFn, TypeAstHole,
TypeAstTuple, TypeAstVar, UntypedArg, UntypedAssert, UntypedAssignment, UntypedClause,
UntypedConstant, UntypedConstantBitArraySegment, UntypedCustomType, UntypedDefinition,
UntypedExpr, UntypedExprBitArraySegment, UntypedFunction, UntypedImport, UntypedModule,
UntypedModuleConstant, UntypedPattern, UntypedPatternBitArraySegment,
UntypedRecordUpdateArg, UntypedStatement, UntypedTailPattern, UntypedTypeAlias, UntypedUse,
UntypedUseAssignment, Use, UseAssignment,
Assert, AssignName, Assignment, BinOp, BitArraySize, CallArg, Constant,
ConstantRecordUpdateArg, Definition, FunctionLiteralKind, Pattern, RecordBeingUpdated,
SrcSpan, Statement, TailPattern, TargetedDefinition, TodoKind, TypeAst, TypeAstConstructor,
TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar, UntypedArg, UntypedAssert,
UntypedAssignment, UntypedClause, UntypedConstant, UntypedConstantBitArraySegment,
UntypedCustomType, UntypedDefinition, UntypedExpr, UntypedExprBitArraySegment,
UntypedFunction, UntypedImport, UntypedModule, UntypedModuleConstant, UntypedPattern,
UntypedPatternBitArraySegment, UntypedRecordUpdateArg, UntypedStatement,
UntypedTailPattern, UntypedTypeAlias, UntypedUse, UntypedUseAssignment, Use, UseAssignment,
},
build::Target,
parse::LiteralFloatValue,
Expand Down Expand Up @@ -978,6 +978,17 @@ pub trait UntypedConstantFolder {
record_constructor: _,
} => self.fold_constant_record(location, module, name, arguments),

Constant::RecordUpdate {
location,
module,
name,
record,
arguments,
tag: (),
type_: (),
field_map: _,
} => self.fold_constant_record_update(location, module, name, record, arguments),

Constant::BitArray { location, segments } => {
self.fold_constant_bit_array(location, segments)
}
Expand Down Expand Up @@ -1072,6 +1083,26 @@ pub trait UntypedConstantFolder {
}
}

fn fold_constant_record_update(
&mut self,
location: SrcSpan,
module: Option<(EcoString, SrcSpan)>,
name: EcoString,
record: Box<UntypedConstant>,
arguments: Vec<ConstantRecordUpdateArg<UntypedConstant>>,
) -> UntypedConstant {
Constant::RecordUpdate {
location,
module,
name,
record,
arguments,
tag: (),
type_: (),
field_map: None,
}
}

fn fold_constant_bit_array(
&mut self,
location: SrcSpan,
Expand Down Expand Up @@ -1170,6 +1201,37 @@ pub trait UntypedConstantFolder {
}
}

Constant::RecordUpdate {
location,
module,
name,
record,
arguments,
tag,
type_,
field_map,
} => {
let record = Box::new(self.fold_constant(*record));
let arguments = arguments
.into_iter()
.map(|arg| ConstantRecordUpdateArg {
label: arg.label,
location: arg.location,
value: self.fold_constant(arg.value),
})
.collect();
Constant::RecordUpdate {
location,
module,
name,
record,
arguments,
tag,
type_,
field_map,
}
}

Constant::BitArray { location, segments } => {
let segments = segments
.into_iter()
Expand Down
11 changes: 11 additions & 0 deletions compiler-core/src/call_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,17 @@ impl<'a> CallGraphBuilder<'a> {
}
}

Constant::RecordUpdate {
record, arguments, ..
} => {
// Visit the record being updated
self.constant(record);
// Visit the update arguments
for argument in arguments {
self.constant(&argument.value);
}
}

Constant::Var {
module: None, name, ..
} => self.referenced(name),
Expand Down
18 changes: 16 additions & 2 deletions compiler-core/src/erlang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1542,11 +1542,19 @@ fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'
},

Constant::Record { tag, arguments, .. } => {
let arguments = arguments
// Spreads are fully expanded during type checking, so we just handle arguments
let arguments_doc = arguments
.iter()
.map(|argument| const_inline(&argument.value, env));
let tag = atom_string(to_snake_case(tag));
tuple(std::iter::once(tag).chain(arguments))
tuple(std::iter::once(tag).chain(arguments_doc))
}

Constant::RecordUpdate { .. } => {
// RecordUpdate should be expanded to Record during type checking, so this should never happen
panic!(
"Encountered RecordUpdate in code generation - this should have been expanded during type checking"
)
}

Constant::Var {
Expand Down Expand Up @@ -3305,6 +3313,12 @@ fn find_referenced_private_functions(
.iter()
.for_each(|argument| find_referenced_private_functions(&argument.value, already_found)),

TypedConstant::RecordUpdate { .. } => {
panic!(
"Encountered RecordUpdate in code generation - this should have been expanded during type checking"
)
}

TypedConstant::StringConcatenation { left, right, .. } => {
find_referenced_private_functions(left, already_found);
find_referenced_private_functions(right, already_found);
Expand Down
6 changes: 6 additions & 0 deletions compiler-core/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,12 @@ impl<'comments> Formatter<'comments> {
.group()
}

Constant::RecordUpdate { .. } => {
panic!(
"Encountered RecordUpdate in code generation - this should have been expanded during type checking"
)
}

Constant::Var {
name, module: None, ..
} => name.to_doc(),
Expand Down
8 changes: 8 additions & 0 deletions compiler-core/src/javascript/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,7 @@ impl<'module, 'a> Generator<'module, 'a> {
return record_constructor(type_.clone(), None, name, arity, self.tracker);
}

// Spreads are fully expanded during type checking, so we just handle arguments
let field_values = arguments
.iter()
.map(|argument| self.constant_expression(context, &argument.value))
Expand All @@ -1855,6 +1856,12 @@ impl<'module, 'a> Generator<'module, 'a> {
}
}

Constant::RecordUpdate { .. } => {
panic!(
"Encountered RecordUpdate in code generation - this should have been expanded during type checking"
)
}

Constant::BitArray { segments, .. } => {
let bit_array = self.constant_bit_array(segments, context);
match context {
Expand Down Expand Up @@ -2253,6 +2260,7 @@ impl<'module, 'a> Generator<'module, 'a> {
return record_constructor(type_.clone(), None, name, arity, self.tracker);
}

// Spreads are fully expanded during type checking, so we just handle arguments
let field_values = arguments
.iter()
.map(|argument| self.guard_constant_expression(&argument.value))
Expand Down
6 changes: 6 additions & 0 deletions compiler-core/src/metadata/module_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,12 @@ impl<'a> ModuleEncoder<'a> {
self.build_type(builder.reborrow().init_type(), type_);
}

Constant::RecordUpdate { .. } => {
panic!(
"Encountered RecordUpdate in code generation - this should have been expanded during type checking"
)
}

Constant::Var {
module,
name,
Expand Down
Loading
Loading