Skip to content

Commit 445747a

Browse files
GearsDatapackslpil
authored andcommitted
Add documentation
1 parent 8399629 commit 445747a

File tree

5 files changed

+119
-31
lines changed

5 files changed

+119
-31
lines changed

compiler-core/src/ast/typed.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ impl TypedExpr {
818818
}
819819

820820
TypedExpr::Call { fun, args, .. } => {
821-
(fun.is_record_builder() || fun.purity().is_pure())
821+
(fun.is_record_builder() || fun.called_function_purity().is_pure())
822822
&& args
823823
.iter()
824824
.all(|argument| argument.value.is_pure_value_constructor())
@@ -855,26 +855,58 @@ impl TypedExpr {
855855
}
856856
}
857857

858-
pub fn purity(&self) -> Purity {
858+
/// Returns the purity of the left hand side of a function call. For example:
859+
///
860+
/// ```gleam
861+
/// io.println("Hello, world!")
862+
/// ```
863+
///
864+
/// Here, the left hand side is `io.println`, which is an impure function,
865+
/// so we would return `Purity::Impure`.
866+
///
867+
/// This does not check whether an expression is pure on its own; for that
868+
/// see `is_pure_value_constructor`.
869+
///
870+
pub fn called_function_purity(&self) -> Purity {
859871
match self {
860-
TypedExpr::Var { constructor, .. } => constructor.purity(),
861-
TypedExpr::ModuleSelect { constructor, .. } => constructor.purity(),
872+
TypedExpr::Var { constructor, .. } => constructor.called_function_purity(),
873+
TypedExpr::ModuleSelect { constructor, .. } => constructor.called_function_purity(),
862874
TypedExpr::Fn { purity, .. } => *purity,
875+
876+
// While we can infer the purity of some of these expressions, such
877+
// as `Case`, in this example:
878+
// ```gleam
879+
// case x {
880+
// True -> io.println
881+
// False -> function.identity
882+
// }("Hello")
883+
// ```
884+
//
885+
// This kind of code is rare in real Gleam applications, and as this
886+
// system is just used for warnings, it is unlikely that supporting
887+
// them will provide any significant benefit to developer experience,
888+
// so we just return `Unknown` for simplicity.
889+
//
890+
TypedExpr::Block { .. }
891+
| TypedExpr::Pipeline { .. }
892+
| TypedExpr::Call { .. }
893+
| TypedExpr::Case { .. }
894+
| TypedExpr::RecordAccess { .. }
895+
| TypedExpr::TupleIndex { .. }
896+
| TypedExpr::Echo { .. } => Purity::Unknown,
897+
898+
// The following expressions are all invalid on the left hand side
899+
// of a call expression: `10()` is not valid Gleam. Therefore, we
900+
// don't really care about any of these as they shouldn't appear in
901+
// well typed Gleam code, and so we can just return `Unknown`.
863902
TypedExpr::Int { .. }
864903
| TypedExpr::Float { .. }
865904
| TypedExpr::String { .. }
866-
| TypedExpr::Block { .. }
867-
| TypedExpr::Pipeline { .. }
868905
| TypedExpr::List { .. }
869-
| TypedExpr::Call { .. }
870906
| TypedExpr::BinOp { .. }
871-
| TypedExpr::Case { .. }
872-
| TypedExpr::RecordAccess { .. }
873907
| TypedExpr::Tuple { .. }
874-
| TypedExpr::TupleIndex { .. }
875908
| TypedExpr::Todo { .. }
876909
| TypedExpr::Panic { .. }
877-
| TypedExpr::Echo { .. }
878910
| TypedExpr::BitArray { .. }
879911
| TypedExpr::RecordUpdate { .. }
880912
| TypedExpr::NegateBool { .. }

compiler-core/src/type_.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -915,10 +915,34 @@ impl ModuleValueConstructor {
915915
}
916916
}
917917

918-
pub fn purity(&self) -> Purity {
918+
/// Returns the purity of this value constructor if it is called as a function.
919+
/// Referencing a module value by itself is always pure, but calling is as a
920+
/// function might not be.
921+
pub fn called_function_purity(&self) -> Purity {
919922
match self {
920-
ModuleValueConstructor::Record { .. } => Purity::Pure,
923+
// If we call a module constant or local variable as a function, we
924+
// no longer have enough information to determine its purity. For
925+
// example:
926+
//
927+
// ```gleam
928+
// const function1 = io.println
929+
// const function2 = function.identity
930+
//
931+
// pub fn main() {
932+
// function1("Hello")
933+
// function2("Hello")
934+
// }
935+
// ```
936+
//
937+
// At this point, we don't have any information about the purity of
938+
// the `function1` and `function2` functions, and must return
939+
// `Purity::Unknown`. See the documentation for the `Purity` type
940+
// for more information on why this is the case.
921941
ModuleValueConstructor::Constant { .. } => Purity::Unknown,
942+
943+
// Constructing records is always pure
944+
ModuleValueConstructor::Record { .. } => Purity::Pure,
945+
922946
ModuleValueConstructor::Fn { purity, .. } => *purity,
923947
}
924948
}
@@ -1385,12 +1409,36 @@ impl ValueConstructor {
13851409
}
13861410
}
13871411

1388-
pub fn purity(&self) -> Purity {
1412+
/// Returns the purity of this value constructor if it is called as a function.
1413+
/// Referencing a value constructor by itself is always pure, but calling is as a
1414+
/// function might not be.
1415+
pub fn called_function_purity(&self) -> Purity {
13891416
match &self.variant {
1417+
// If we call a module constant or local variable as a function, we
1418+
// no longer have enough information to determine its purity. For
1419+
// example:
1420+
//
1421+
// ```gleam
1422+
// const function1 = io.println
1423+
// const function2 = function.identity
1424+
//
1425+
// pub fn main() {
1426+
// function1("Hello")
1427+
// function2("Hello")
1428+
// }
1429+
// ```
1430+
//
1431+
// At this point, we don't have any information about the purity of
1432+
// the `function1` and `function2` functions, and must return
1433+
// `Purity::Unknown`. See the documentation for the `Purity` type
1434+
// for more information on why this is the case.
13901435
ValueConstructorVariant::LocalVariable { .. }
13911436
| ValueConstructorVariant::ModuleConstant { .. }
13921437
| ValueConstructorVariant::LocalConstant { .. } => Purity::Unknown,
1438+
1439+
// Constructing records is always pure
13931440
ValueConstructorVariant::Record { .. } => Purity::Pure,
1441+
13941442
ValueConstructorVariant::ModuleFn { purity, .. } => *purity,
13951443
}
13961444
}

compiler-core/src/type_/expression.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ impl Purity {
121121
}
122122
}
123123

124-
pub fn merge(&mut self, other: Purity) {
125-
let new_purity = match (*self, other) {
124+
#[must_use]
125+
pub fn merge(self, other: Purity) -> Purity {
126+
match (self, other) {
126127
// If we call a trusted pure function, the current function remains pure
127128
(Purity::Pure, Purity::TrustedPure) => Purity::Pure,
128129
(Purity::Pure, other) => other,
@@ -138,9 +139,7 @@ impl Purity {
138139
// purity of, we are now certain that it is impure.
139140
(Purity::Unknown, Purity::Impure) => Purity::Impure,
140141
(Purity::Unknown, _) => Purity::Impure,
141-
};
142-
143-
*self = new_purity;
142+
}
144143
}
145144
}
146145

@@ -941,7 +940,24 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
941940
let outer_purity = self.purity;
942941

943942
// If an anonymous function can panic, that doesn't mean that the outer
944-
// function can too, so we track the purity separately.
943+
// function can too, so we track the purity separately. For example, in
944+
// this code:
945+
//
946+
// ```gleam
947+
// pub fn divide_partial(dividend: Int) {
948+
// fn(divisor) {
949+
// case divisor {
950+
// 0 -> panic as "Cannot divide by 0"
951+
// _ -> dividend / divisor
952+
// }
953+
// }
954+
// }
955+
// ```
956+
//
957+
// Although the `divide_partial` function uses the `panic` keyword, it is
958+
// actually pure. Only the anonymous function that it constructs is impure;
959+
// constructing and returning it does not have any side effects, so there is
960+
// no way for a call to `divide_partial` to produce any side effects.
945961
self.purity = Purity::Pure;
946962

947963
let (args, body) = match self.do_infer_fn(args, expected_args, body, &return_annotation) {
@@ -1040,7 +1056,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
10401056
});
10411057
}
10421058

1043-
self.purity.merge(fun.purity());
1059+
self.purity = self.purity.merge(fun.called_function_purity());
10441060

10451061
TypedExpr::Call {
10461062
location,

compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_function_call_if_panic_is_last_argument_1.snap

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,3 @@ warning: Unreachable code
2121

2222
This function call is unreachable because its last argument always panics.
2323
Your code will crash before reaching this point.
24-
25-
warning: Unused pure function call
26-
┌─ /src/warning/wrn.gleam:4:11
27-
28-
4 │ wibble(1, panic)
29-
│ ^^^^^^^^^^^^^^^^ This pure function call is never used
30-
31-
Gleam is an immutable language, meaning functions cannot mutate state
32-
simply by being called. This function is pure, so it must be assigned to a
33-
variable to have an effect on the program.

compiler-core/src/type_/tests/warnings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3086,6 +3086,7 @@ fn add(a, b) { a + b }
30863086
pub fn main() {
30873087
add(1, 2)
30883088
Nil
3089+
}
30893090
"
30903091
);
30913092
}
@@ -3096,6 +3097,7 @@ fn bit_array_truncated_segment_in_bytes() {
30963097
"
30973098
pub fn main() {
30983099
<<258:size(8)>>
3100+
}
30993101
"
31003102
);
31013103
}

0 commit comments

Comments
 (0)