Upstream gleam-lang/gleam state of the touched files before fe0ae5310a

This commit is contained in:
upstream committed 2026-10-10 13:50:53 +02:00
1 parent 88fec07fd0
commit 305f0f4ec3
3 files changed
+1201

No files matched your search

+675
View File
@@ -0,0 +1,675 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2020 The Gleam contributors
use super::*;
use crate::analyse::Inferred;
use crate::type_::{FieldMap, HasType};
pub type TypedConstant = Constant<Arc<Type>>;
pub type UntypedConstant = Constant<()>;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Constant<T> {
Int {
location: SrcSpan,
value: EcoString,
int_value: BigInt,
},
Float {
location: SrcSpan,
value: EcoString,
float_value: LiteralFloatValue,
},
String {
location: SrcSpan,
value: EcoString,
},
Tuple {
location: SrcSpan,
elements: Vec<Self>,
type_: T,
},
List {
location: SrcSpan,
elements: Vec<Self>,
type_: T,
tail: Option<Box<Self>>,
},
Record {
location: SrcSpan,
arguments_start_position: u32,
module: Option<(EcoString, SrcSpan)>,
name: EcoString,
/// These are the arguments used when calling the record.
/// If the record is not being called to build a value, then this will
/// be `None`. A couple of examples:
/// ```gleam
/// pub const a = Wibble
/// // arguments: None
///
/// pub const b = Wibble()
/// // arguments: Some(vec![])
///
/// pub const c = Wibble(1, 2)
/// // arguments: Some(vec![1, 2])
/// ```
arguments: Option<Vec<CallArg<Self>>>,
type_: T,
field_map: Inferred<FieldMap>,
record_constructor: Option<Box<ValueConstructor>>,
},
RecordUpdate {
location: SrcSpan,
constructor_location: SrcSpan,
module: Option<(EcoString, SrcSpan)>,
name: EcoString,
record: RecordBeingUpdated<Self>,
arguments: Vec<RecordUpdateArg<Self>>,
type_: T,
field_map: Inferred<FieldMap>,
},
BitArray {
location: SrcSpan,
segments: Vec<BitArraySegment<Self, T>>,
},
Var {
location: SrcSpan,
module: Option<(EcoString, SrcSpan)>,
name: EcoString,
constructor: Option<Box<ValueConstructor>>,
type_: T,
},
/// A constant binary operation.
/// Most binops are not supported in constants yet, however we parse them
/// and produce an error during type analysis rather than during parsing so
/// we can get better error messages and language server code actions.
BinaryOperator {
location: SrcSpan,
operator_start: u32,
operator: BinOp,
left: Box<Self>,
right: Box<Self>,
type_: T,
},
/// A placeholder constant used to allow module analysis to continue
/// even when there are type errors. Should never end up in generated code.
Invalid {
location: SrcSpan,
type_: T,
/// Extra information about the invalid expression, useful for providing
/// addition help or information, such as code actions to fix invalid
/// states.
extra_information: Option<InvalidExpression>,
},
Todo {
location: SrcSpan,
type_: T,
message: Option<Box<Self>>,
},
}
impl<T> Constant<T> {
pub fn bin_op_precedence(&self) -> u8 {
match self {
Self::BinaryOperator { operator, .. } => operator.precedence(),
Self::Int { .. }
| Self::Float { .. }
| Self::String { .. }
| Self::Var { .. }
| Self::List { .. }
| Self::Tuple { .. }
| Self::Todo { .. }
| Self::BitArray { .. }
| Self::Invalid { .. }
| Self::Record { .. }
| Self::RecordUpdate { .. } => u8::MAX,
}
}
pub fn bin_op_name(&self) -> Option<BinOp> {
if let Self::BinaryOperator { operator, .. } = self {
Some(*operator)
} else {
None
}
}
pub fn is_tuple(&self) -> bool {
match self {
Constant::Tuple { .. } => true,
Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::List { .. }
| Constant::Record { .. }
| Constant::RecordUpdate { .. }
| Constant::BitArray { .. }
| Constant::Var { .. }
| Constant::BinaryOperator { .. }
| Constant::Invalid { .. }
| Constant::Todo { .. } => false,
}
}
pub fn is_binop(&self) -> bool {
match self {
Constant::BinaryOperator { .. } => true,
Constant::Tuple { .. }
| Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::List { .. }
| Constant::Record { .. }
| Constant::RecordUpdate { .. }
| Constant::BitArray { .. }
| Constant::Var { .. }
| Constant::Invalid { .. }
| Constant::Todo { .. } => false,
}
}
}
impl TypedConstant {
pub fn type_(&self) -> Arc<Type> {
match self {
Constant::Int { .. } => type_::int(),
Constant::Float { .. } => type_::float(),
Constant::String { .. } => type_::string(),
Constant::BitArray { .. } => type_::bit_array(),
Constant::List { type_, .. }
| Constant::BinaryOperator { type_, .. }
| Constant::Tuple { type_, .. }
| Constant::Record { type_, .. }
| Constant::RecordUpdate { type_, .. }
| Constant::Var { type_, .. }
| Constant::Todo { type_, .. }
| Constant::Invalid { type_, .. } => type_.clone(),
}
}
pub fn find_node(&self, byte_index: u32) -> Option<Located<'_>> {
if !self.location().contains(byte_index) {
return None;
}
Some(match self {
Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::Invalid { .. } => Located::Constant(self),
Constant::Todo { message, .. } => message
.iter()
.find_map(|message| message.find_node(byte_index))
.unwrap_or(Located::Constant(self)),
Constant::Var {
module: Some((module_alias, location)),
constructor: Some(constructor),
..
}
| Constant::Record {
module: Some((module_alias, location)),
record_constructor: Some(constructor),
..
} if location.contains(byte_index) => match &constructor.variant {
ValueConstructorVariant::ModuleConstant { module, .. }
| ValueConstructorVariant::ModuleFn { module, .. }
| ValueConstructorVariant::Record { module, .. } => Located::ModuleName {
location: *location,
module_name: module.clone(),
module_alias: module_alias.clone(),
layer: Layer::Value,
},
ValueConstructorVariant::LocalVariable { .. } => Located::Constant(self),
},
Constant::Var { .. } => Located::Constant(self),
Constant::Tuple { elements, .. } => elements
.iter()
.find_map(|element| element.find_node(byte_index))
.unwrap_or(Located::Constant(self)),
Constant::List { elements, tail, .. } => elements
.iter()
.find_map(|element| element.find_node(byte_index))
.or_else(|| tail.as_deref().and_then(|tail| tail.find_node(byte_index)))
.unwrap_or(Located::Constant(self)),
Constant::Record {
arguments,
type_,
name,
..
} => arguments
.iter()
.flatten()
.find_map(|argument| argument.find_node(byte_index, type_, name))
.unwrap_or(Located::Constant(self)),
Constant::RecordUpdate {
record, arguments, ..
} => record
.base
.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))
.unwrap_or(Located::Constant(self)),
Constant::BinaryOperator { left, right, .. } => left
.find_node(byte_index)
.or_else(|| right.find_node(byte_index))
.unwrap_or(Located::Constant(self)),
})
}
pub fn definition_location(&self) -> Option<DefinitionLocation> {
match self {
Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::Tuple { .. }
| Constant::List { .. }
| Constant::BitArray { .. }
| Constant::Todo { .. }
| Constant::BinaryOperator { .. }
| Constant::Invalid { .. } => None,
Constant::Record {
record_constructor: value_constructor,
..
}
| Constant::Var {
constructor: value_constructor,
..
} => value_constructor
.as_ref()
.map(|constructor| constructor.definition_location()),
Constant::RecordUpdate { .. } => None,
}
}
pub(crate) fn referenced_variables(&self) -> imbl::HashSet<&EcoString> {
match self {
Constant::Var { name, .. } => imbl::hashset![name],
Constant::Invalid { .. }
| Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. } => imbl::hashset![],
Constant::Todo { message, .. } => message
.as_ref()
.map(|message| message.referenced_variables())
.unwrap_or(imbl::hashset![]),
Constant::Tuple { elements, .. } => elements
.iter()
.map(|element| element.referenced_variables())
.fold(imbl::hashset![], imbl::HashSet::union),
Constant::List { elements, tail, .. } => elements
.iter()
.map(|element| element.referenced_variables())
.chain(tail.iter().map(|tail| tail.referenced_variables()))
.fold(imbl::hashset![], imbl::HashSet::union),
Constant::Record { arguments, .. } => arguments
.iter()
.flatten()
.map(|argument| argument.value.referenced_variables())
.fold(imbl::hashset![], imbl::HashSet::union),
Constant::RecordUpdate {
record, arguments, ..
} => record.base.referenced_variables().union(
arguments
.iter()
.map(|arg| arg.value.referenced_variables())
.fold(imbl::hashset![], imbl::HashSet::union),
),
Constant::BitArray { segments, .. } => segments
.iter()
.map(|segment| {
segment
.options
.iter()
.map(|option| option.referenced_variables())
.fold(segment.value.referenced_variables(), imbl::HashSet::union)
})
.fold(imbl::hashset![], imbl::HashSet::union),
Constant::BinaryOperator { left, right, .. } => left
.referenced_variables()
.union(right.referenced_variables()),
}
}
pub(crate) fn syntactically_eq(&self, other: &Self) -> bool {
match (self, other) {
(
Constant::Todo { message, .. },
Constant::Todo {
message: other_message,
..
},
) => match (message, other_message) {
(None, None) => true,
(Some(_), None) | (None, Some(_)) => false,
(Some(message), Some(other_message)) => message.syntactically_eq(other_message),
},
(Constant::Todo { .. }, _) => false,
(Constant::Int { int_value: n, .. }, Constant::Int { int_value: m, .. }) => n == m,
(Constant::Int { .. }, _) => false,
(Constant::Float { float_value: n, .. }, Constant::Float { float_value: m, .. }) => {
n == m
}
(Constant::Float { .. }, _) => false,
(
Constant::String { value, .. },
Constant::String {
value: other_value, ..
},
) => value == other_value,
(Constant::String { .. }, _) => false,
(
Constant::Tuple { elements, .. },
Constant::Tuple {
elements: other_elements,
..
},
) => pairwise_all(elements, other_elements, |(one, other)| {
one.syntactically_eq(other)
}),
(Constant::Tuple { .. }, _) => false,
(
Constant::List { elements, tail, .. },
Constant::List {
elements: other_elements,
tail: other_tail,
..
},
) => {
let tails_are_equal = match (tail, other_tail) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some(tail), Some(other_tail)) => tail.syntactically_eq(other_tail),
};
tails_are_equal
&& pairwise_all(elements, other_elements, |(one, other)| {
one.syntactically_eq(other)
})
}
(Constant::List { .. }, _) => false,
(
Constant::Record {
module,
name,
arguments,
..
},
Constant::Record {
module: other_module,
name: other_name,
arguments: other_arguments,
..
},
) => {
let modules_are_equal = match (module, other_module) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some((one, _)), Some((other, _))) => one == other,
};
let arguments_are_equal = match (arguments, other_arguments) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some(arguments), Some(other_arguments)) => {
modules_are_equal
&& name == other_name
&& pairwise_all(arguments, other_arguments, |(one, other)| {
one.label == other.label && one.value.syntactically_eq(&other.value)
})
}
};
modules_are_equal && arguments_are_equal
}
(Constant::Record { .. }, _) => false,
(
Constant::RecordUpdate {
module,
name,
record,
arguments,
..
},
Constant::RecordUpdate {
module: other_module,
name: other_name,
record: other_record,
arguments: other_arguments,
..
},
) => {
let modules_are_equal = match (module, other_module) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some((one, _)), Some((other, _))) => one == other,
};
modules_are_equal
&& name == other_name
&& record.base.syntactically_eq(&other_record.base)
&& pairwise_all(arguments, other_arguments, |(one, other)| {
one.label == other.label && one.value.syntactically_eq(&other.value)
})
}
(Constant::RecordUpdate { .. }, _) => false,
(
Constant::BitArray { segments, .. },
Constant::BitArray {
segments: other_segments,
..
},
) => pairwise_all(segments, other_segments, |(one, other)| {
one.syntactically_eq(other)
}),
(Constant::BitArray { .. }, _) => false,
(
Constant::Var { module, name, .. },
Constant::Var {
module: other_module,
name: other_name,
..
},
) => {
let modules_are_equal = match (module, other_module) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some((one, _)), Some((other, _))) => one == other,
};
modules_are_equal && name == other_name
}
(Constant::Var { .. }, _) => false,
(
Constant::BinaryOperator {
left,
right,
operator,
..
},
Constant::BinaryOperator {
left: other_left,
right: other_right,
operator: other_operator,
..
},
) => {
operator == other_operator
&& left.syntactically_eq(other_left)
&& right.syntactically_eq(other_right)
}
(Constant::BinaryOperator { .. }, _) => false,
(Constant::Invalid { .. }, _) => false,
}
}
/// If the constant is a list whose elements are known at compile time, this
/// returns a vec of all its elements.
/// This can happen with:
/// - literal lists: `[1, 2]`
/// - literal lists whose tail is also known at compile time:
/// - `[1, 2, ..[3, 4]]`
/// - `[1, 2, ..a_known_module_constant]`
///
pub fn list_elements(&self) -> Option<Vec<&Self>> {
match self {
Constant::List { elements, tail, .. } => {
if let Some(tail) = tail {
// There's a tail, if it cannot be known at compile time,
// then this entire list cannot be known at compile time!
let tail_elements = tail.list_elements()?;
Some(elements.iter().chain(tail_elements).collect())
} else {
// There's no tail, we just return the elements
Some(elements.iter().collect())
}
}
Constant::Var {
constructor: Some(constructor),
..
} => match &constructor.variant {
ValueConstructorVariant::ModuleConstant { literal, .. } => literal.list_elements(),
ValueConstructorVariant::LocalVariable { .. }
| ValueConstructorVariant::ModuleFn { .. }
| ValueConstructorVariant::Record { .. } => None,
},
Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::Tuple { .. }
| Constant::Record { .. }
| Constant::RecordUpdate { .. }
| Constant::BitArray { .. }
| Constant::BinaryOperator { .. }
| Constant::Var { .. }
| Constant::Todo { .. }
| Constant::Invalid { .. } => None,
}
}
/// If the constant is a record or record update this returns its tag.
/// It might return `None` if the record constructor couldn't be inferred.
/// For example, if someone wrote a variant that doesn't exist:
///
/// ```gleam
/// pub const wibble = ThisIsNotDefinedAnywhere(1, 2)
/// ```
///
/// In this case the record wouldn't have a constructor, as there's no
/// custom type defining it anywhere!
///
pub(crate) fn constant_record_tag(&self) -> Option<EcoString> {
if let Constant::Record {
record_constructor: Some(constructor),
..
} = self
&& let ValueConstructorVariant::Record { name, .. } = &constructor.variant
{
Some(name.clone())
} else if let Constant::RecordUpdate { name, .. } = self {
Some(name.clone())
} else {
None
}
}
}
impl HasType for TypedConstant {
fn type_(&self) -> Arc<Type> {
self.type_()
}
}
impl<A> Constant<A> {
pub fn location(&self) -> SrcSpan {
match self {
Constant::Int { location, .. }
| Constant::List { location, .. }
| Constant::Float { location, .. }
| Constant::Tuple { location, .. }
| Constant::String { location, .. }
| Constant::Record { location, .. }
| Constant::RecordUpdate { location, .. }
| Constant::BitArray { location, .. }
| Constant::Var { location, .. }
| Constant::Invalid { location, .. }
| Constant::Todo { location, .. }
| Constant::BinaryOperator { location, .. } => *location,
}
}
#[must_use]
pub fn can_have_multiple_per_line(&self) -> bool {
match self {
Constant::Int { .. }
| Constant::Float { .. }
| Constant::String { .. }
| Constant::Var { .. } => true,
Constant::Tuple { .. }
| Constant::Todo { .. }
| Constant::List { .. }
| Constant::Record { .. }
| Constant::RecordUpdate { .. }
| Constant::BitArray { .. }
| Constant::BinaryOperator { .. }
| Constant::Invalid { .. } => false,
}
}
}
impl<A> HasLocation for Constant<A> {
fn location(&self) -> SrcSpan {
self.location()
}
}
impl<A> bit_array::GetLiteralValue for Constant<A> {
fn as_int_literal(&self) -> Option<BigInt> {
if let Constant::Int { int_value, .. } = self {
Some(int_value.clone())
} else {
None
}
}
}
+437
View File
@@ -0,0 +1,437 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2026 The Gleam contributors
use crate::{
ast::{BinOp, ClauseGuard, TypedClauseGuard, TypedExpr, UntypedClauseGuard},
reference::ReferenceKind,
type_::{
Error, ExprTyper, FieldAccessUsage, ModuleValueConstructor, RecordAccessor, Type,
ValueConstructorVariant, ValueUsage, bool,
error::{FeatureKind, convert_unify_error},
float, int, string, unify,
},
};
use ecow::EcoString;
use src_span::SrcSpan;
pub struct GuardTyper<'expression_typer, 'env, 'module> {
typer: &'expression_typer mut ExprTyper<'env, 'module>,
}
impl<'expression_typer, 'env, 'module> GuardTyper<'expression_typer, 'env, 'module> {
pub fn new(typer: &'expression_typer mut ExprTyper<'env, 'module>) -> Self {
Self { typer }
}
pub fn infer(mut self, guard: UntypedClauseGuard) -> TypedClauseGuard {
self.do_infer(guard)
}
fn do_infer(&mut self, guard: UntypedClauseGuard) -> TypedClauseGuard {
match guard {
ClauseGuard::Invalid { .. } => {
unreachable!("untyped guard should never be invalid")
}
ClauseGuard::Var { location, name, .. } => {
match self.infer_clause_guard_variable(name, location) {
Ok(variable) => variable,
Err(error) => {
self.typer.problems.error(error);
ClauseGuard::Invalid {
location,
type_: self.typer.new_unbound_var(),
}
}
}
}
ClauseGuard::TupleIndex {
location,
tuple,
index,
..
} => {
let tuple = self.do_infer(*tuple);
let index_type = match tuple.type_().as_ref() {
Type::Tuple { elements } => match elements.get(index as usize) {
Some(type_) => type_.clone(),
// If the index is outside the tuple range, then we
// report the error and return an unbound type to keep
// going.
None => {
self.typer.problems.error(Error::OutOfBoundsTupleIndex {
location,
index,
size: elements.len(),
});
self.typer.new_unbound_var()
}
},
tuple_type if tuple_type.is_unbound() => {
self.typer.problems.error(Error::NotATupleUnbound {
location: tuple.location(),
});
self.typer.new_unbound_var()
}
Type::Named { .. } | Type::Fn { .. } | Type::Var { .. } => {
self.typer.problems.error(Error::NotATuple {
location: tuple.location(),
given: tuple.type_(),
});
self.typer.new_unbound_var()
}
};
ClauseGuard::TupleIndex {
location,
index,
type_: index_type,
tuple: Box::new(tuple),
}
}
ClauseGuard::FieldAccess {
label_location,
label,
container,
index: _,
type_: (),
} => {
let container_location = container.location();
let result = if let ClauseGuard::Var { name, location, .. } = *container {
// If the container looks like a regular variable, then this
// could either be a module select, or a record access.
match self.infer_clause_guard_variable(name.clone(), location) {
// If the variable itself cannot be inferred as one, then
// it could really be a module select. We try that one
// as an alternative.
Err(error) => self.infer_guard_module_access(
name,
label,
location,
label_location,
error,
),
// Otherwise that's a proper variable and not a module name,
// so the whole expression has to be inferred as a regular
// record access.
Ok(variable) => {
self.infer_guard_record_access(variable, label, label_location)
}
}
} else {
// If it doesn't this has to be a regular record access and
// we try and infer it as such.
let inferred_container = self.do_infer(*container.clone());
self.infer_guard_record_access(inferred_container, label, label_location)
};
match result {
Ok(inferred) => inferred,
Err(error) => {
self.typer.problems.error(error);
ClauseGuard::Invalid {
location: container_location.merge(&label_location),
type_: self.typer.new_unbound_var(),
}
}
}
}
ClauseGuard::ModuleSelect { .. } => {
unreachable!("untyped guard should never be module select")
}
ClauseGuard::Not {
location,
expression,
} => {
let expression = self.do_infer(*expression);
if let Err(error) = unify(bool(), expression.type_()) {
self.typer
.problems
.error(convert_unify_error(error, expression.location()));
}
ClauseGuard::Not {
location,
expression: Box::new(expression),
}
}
ClauseGuard::Constant(constant) => {
ClauseGuard::Constant(self.typer.infer_const(&None, constant))
}
ClauseGuard::Block { value, location } => ClauseGuard::Block {
location,
value: Box::new(self.do_infer(*value)),
},
ClauseGuard::BinaryOperator {
location,
operator,
operator_start,
left,
right,
} => {
let left = self.do_infer(*left);
let right = self.do_infer(*right);
match operator {
BinOp::And | BinOp::Or => {
if let Err(error) = unify(bool(), left.type_()) {
self.typer
.problems
.error(convert_unify_error(error, left.location()));
}
if let Err(error) = unify(bool(), right.type_()) {
self.typer
.problems
.error(convert_unify_error(error, right.location()));
}
}
BinOp::Eq | BinOp::NotEq => {
if let Err(error) = unify(left.type_(), right.type_()) {
self.typer
.problems
.error(convert_unify_error(error, right.location()));
}
}
BinOp::GtInt
| BinOp::GtEqInt
| BinOp::LtInt
| BinOp::LtEqInt
| BinOp::AddInt
| BinOp::SubInt
| BinOp::DivInt
| BinOp::MultInt
| BinOp::RemainderInt => {
self.typer
.track_feature_usage(FeatureKind::ArithmeticInGuards, location);
// If both operands are floats, then we use a more specialised
// error.
if left.type_().is_float() && right.type_().is_float() {
self.typer.problems.error(Error::IntOperatorOnFloats {
operator,
location: SrcSpan::new(
operator_start,
operator_start + operator.size(),
),
});
} else {
if let Err(error) = unify(int(), left.type_()) {
self.typer
.problems
.error(convert_unify_error(error, left.location()));
}
if let Err(error) = unify(int(), right.type_()) {
self.typer
.problems
.error(convert_unify_error(error, right.location()));
}
}
}
BinOp::GtFloat
| BinOp::GtEqFloat
| BinOp::LtFloat
| BinOp::LtEqFloat
| BinOp::AddFloat
| BinOp::SubFloat
| BinOp::DivFloat
| BinOp::MultFloat => {
self.typer
.track_feature_usage(FeatureKind::ArithmeticInGuards, location);
// If both operands are int then we use a more specialised
// error
if left.type_().is_int() && right.type_().is_int() {
self.typer.problems.error(Error::FloatOperatorOnInts {
operator,
location: SrcSpan::new(
operator_start,
operator_start + operator.size(),
),
});
} else {
if let Err(error) = unify(float(), left.type_()) {
self.typer
.problems
.error(convert_unify_error(error, left.location()));
}
if let Err(error) = unify(float(), right.type_()) {
self.typer
.problems
.error(convert_unify_error(error, right.location()));
}
}
}
BinOp::Concatenate => {
self.typer
.track_feature_usage(FeatureKind::ConcatenateInGuards, location);
if let Err(error) = unify(string(), left.type_()) {
self.typer
.problems
.error(convert_unify_error(error, left.location()));
}
if let Err(error) = unify(string(), right.type_()) {
self.typer
.problems
.error(convert_unify_error(error, right.location()));
}
}
}
ClauseGuard::BinaryOperator {
location,
operator,
operator_start,
left: Box::new(left),
right: Box::new(right),
}
}
}
}
fn infer_clause_guard_variable(
&mut self,
name: EcoString,
location: SrcSpan,
) -> Result<TypedClauseGuard, Error> {
let constructor =
self.typer
.infer_value_constructor(&None, &name, &location, ValueUsage::Other)?;
// We cannot support all values in guard expressions as the BEAM does not
let (definition_location, origin) = match &constructor.variant {
ValueConstructorVariant::LocalVariable { location, origin } => {
(*location, origin.clone())
}
ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => {
return Err(Error::NonLocalClauseGuardVariable { location, name });
}
ValueConstructorVariant::ModuleConstant { literal, .. } => {
return Ok(ClauseGuard::Constant(literal.clone()));
}
};
Ok(ClauseGuard::Var {
location,
name,
origin,
type_: constructor.type_,
definition_location,
})
}
fn infer_guard_record_access(
&mut self,
container: TypedClauseGuard,
label: EcoString,
location: SrcSpan,
) -> Result<TypedClauseGuard, Error> {
let container = Box::new(container);
let container_type = container.type_();
let RecordAccessor {
index,
label,
type_,
documentation: _,
} = self.typer.infer_known_record_access(
container_type,
container.location(),
FieldAccessUsage::Other,
location,
label,
)?;
Ok(ClauseGuard::FieldAccess {
container,
label,
index: Some(index),
label_location: location,
type_,
})
}
fn infer_guard_module_access(
&mut self,
name: EcoString,
label: EcoString,
module_location: SrcSpan,
label_location: SrcSpan,
record_access_error: Error,
) -> Result<TypedClauseGuard, Error> {
let module_access = self
.typer
.infer_module_access(&name, label, &module_location, label_location)
.and_then(|module_select| {
if let TypedExpr::ModuleSelect {
location,
field_start: _,
type_,
label,
module_name,
module_alias,
constructor,
} = module_select
{
match constructor {
ModuleValueConstructor::Constant {
literal,
location: definition_location,
..
} => {
self.typer.environment.references.register_value_reference(
module_name.clone(),
label.clone(),
&label,
label_location,
ReferenceKind::Qualified {
module_alias: module_alias.clone(),
module_location,
},
);
Ok(ClauseGuard::ModuleSelect {
location,
field_start: label_location.start,
definition_location,
type_,
label,
module_name,
module_alias,
literal,
})
}
ModuleValueConstructor::Record { .. }
| ModuleValueConstructor::Fn { .. } => {
Err(Error::RecordAccessUnknownType { location })
}
}
} else {
Err(Error::RecordAccessUnknownType {
location: module_location,
})
}
});
// If the name is in the environment, use the original error from
// inferring the record access, so that we can suggest possible
// misspellings of field names
if self.typer.environment.scope.contains_key(&name) {
module_access.map_err(|_| record_access_error)
} else {
module_access
}
}
}
+89
View File
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 The Gleam contributors
use crate::{assert_module_error, assert_module_infer};
#[test]
fn nested_record_access() {
assert_module_infer!(
r#"
pub type A {
A(b: B)
}
pub type B {
B(c: C)
}
pub type C {
C(d: Bool)
}
pub fn a(a: A) {
case a {
_ if a.b.c.d -> 1
_ -> 0
}
}
"#,
vec![
("A", "fn(B) -> A"),
("B", "fn(C) -> B"),
("C", "fn(Bool) -> C"),
("a", "fn(A) -> Int"),
],
);
}
#[test]
fn string_variable_access() {
assert_module_error!(
r#"
pub fn a(a: String) {
case a {
_ if a.b -> 1
_ -> 0
}
}
"#
);
}
#[test]
fn qualified_record() {
assert_module_infer!(
("wibble", "pub type Wibble { Wibble Wobble }"),
"
import wibble
pub fn main(wibble: wibble.Wibble) {
case wibble {
w if w == wibble.Wobble -> True
_ -> False
}
}
",
vec![("main", "fn(Wibble) -> Bool")]
);
}
#[test]
fn qualified_record_with_arguments() {
assert_module_infer!(
(
"wibble",
"pub type Wibble { Wibble(Int) Wobble(Int, Float) }"
),
"
import wibble
pub fn main(wibble: wibble.Wibble) {
case wibble {
w if w == wibble.Wobble(1, 3.8) -> True
_ -> False
}
}
",
vec![("main", "fn(Wibble) -> Bool")]
);
}