Upstream gleam-lang/gleam state of the touched files before fe0ae5310a
This commit is contained in:
1 parent
88fec07fd0
commit
305f0f4ec3
3 files changed
+1201
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user