Skip to content

Commit 9fc26c4

Browse files
committed
Add suggestions for UntypedExpr::Call that check the arity.
Before this commit, all functions / constructors were suggested even if they do not have the correct arity. With this change, only functions / constructors with the correct arity are suggested. However, even if the arity is correct the type of arguments can mismatch, resulting in incorrect suggestions. ```gleam // fn to_string(Int) -> String import gleam/int // fn to_string(Float) -> String import gleam/float pub fn main() -> Nil { to_string(1) } error: Unknown variable ┌─ /path/to/project/src/project.gleam:8:3 │ 8 │ to_string(1) │ ^^^^^^^^^ The name `to_string` is not in scope here. Consider using one of these variables: int.to_string float.to_string ```
1 parent 3e8ddad commit 9fc26c4

File tree

2 files changed

+126
-35
lines changed

2 files changed

+126
-35
lines changed

compiler-core/src/type_.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,16 @@ pub enum FieldAccessUsage {
16651665
Other,
16661666
}
16671667

1668+
/// This is used to know when a variable is used as a value or as a call:
1669+
/// function call, a pipeline or a custom type variant constructor
1670+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1671+
pub enum VarUsage {
1672+
/// Used as `variable()`
1673+
Call { arity: usize },
1674+
/// Used as `variable`
1675+
Other,
1676+
}
1677+
16681678
/// Verify that a value is suitable to be used as a main function.
16691679
fn assert_suitable_main_function(
16701680
value: &ValueConstructor,

compiler-core/src/type_/expression.rs

Lines changed: 116 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
432432
message,
433433
} => Ok(self.infer_echo(location, keyword_end, expression, message)),
434434

435-
UntypedExpr::Var { location, name, .. } => {
436-
self.infer_var(name, location, ReferenceRegistration::RegisterReferences)
437-
}
435+
UntypedExpr::Var { location, name, .. } => self.infer_var(
436+
name,
437+
location,
438+
VarUsage::Other,
439+
ReferenceRegistration::RegisterReferences,
440+
),
438441

439442
UntypedExpr::Int {
440443
location,
@@ -1247,10 +1250,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
12471250
&mut self,
12481251
name: EcoString,
12491252
location: SrcSpan,
1253+
var_usage: VarUsage,
12501254
register_reference: ReferenceRegistration,
12511255
) -> Result<TypedExpr, Error> {
1252-
let constructor =
1253-
self.do_infer_value_constructor(&None, &name, &location, register_reference)?;
1256+
let constructor = self.do_infer_value_constructor(
1257+
&None,
1258+
&name,
1259+
&location,
1260+
var_usage,
1261+
register_reference,
1262+
)?;
12541263
self.narrow_implementations(location, &constructor.variant)?;
12551264
Ok(TypedExpr::Var {
12561265
constructor,
@@ -1345,6 +1354,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
13451354
UntypedExpr::Var { location, name } => self.infer_var(
13461355
name,
13471356
location,
1357+
VarUsage::Other,
13481358
ReferenceRegistration::DoNotRegisterReferences,
13491359
),
13501360
_ => self.infer_or_error(container),
@@ -2309,7 +2319,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
23092319
fn infer_clause_guard(&mut self, guard: UntypedClauseGuard) -> Result<TypedClauseGuard, Error> {
23102320
match guard {
23112321
ClauseGuard::Var { location, name, .. } => {
2312-
let constructor = self.infer_value_constructor(&None, &name, &location)?;
2322+
let constructor =
2323+
self.infer_value_constructor(&None, &name, &location, VarUsage::Other)?;
23132324

23142325
// We cannot support all values in guard expressions as the BEAM does not
23152326
let definition_location = match &constructor.variant {
@@ -3459,11 +3470,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
34593470
module: &Option<(EcoString, SrcSpan)>,
34603471
name: &EcoString,
34613472
location: &SrcSpan,
3473+
var_usage: VarUsage,
34623474
) -> Result<ValueConstructor, Error> {
34633475
self.do_infer_value_constructor(
34643476
module,
34653477
name,
34663478
location,
3479+
var_usage,
34673480
ReferenceRegistration::RegisterReferences,
34683481
)
34693482
}
@@ -3473,6 +3486,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
34733486
module: &Option<(EcoString, SrcSpan)>,
34743487
name: &EcoString,
34753488
location: &SrcSpan,
3489+
var_usage: VarUsage,
34763490
register_reference: ReferenceRegistration,
34773491
) -> Result<ValueConstructor, Error> {
34783492
let constructor = match module {
@@ -3481,7 +3495,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
34813495
.environment
34823496
.get_variable(name)
34833497
.cloned()
3484-
.ok_or_else(|| self.report_name_error(name, location))?,
3498+
.ok_or_else(|| self.report_name_error(name, location, var_usage))?,
34853499

34863500
// Look in an imported module for a binding with this name
34873501
Some((module_name, module_location)) => {
@@ -3609,7 +3623,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
36093623
}
36103624
}
36113625

3612-
fn report_name_error(&mut self, name: &EcoString, location: &SrcSpan) -> Error {
3626+
fn report_name_error(
3627+
&mut self,
3628+
name: &EcoString,
3629+
location: &SrcSpan,
3630+
var_usage: VarUsage,
3631+
) -> Error {
36133632
// First try to see if this is a module alias:
36143633
// `import gleam/io`
36153634
// `io.debug(io)`
@@ -3620,30 +3639,59 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
36203639
location: *location,
36213640
name: name.clone(),
36223641
},
3623-
None => Error::UnknownVariable {
3624-
location: *location,
3625-
name: name.clone(),
3626-
variables: self.environment.local_value_names(),
3627-
discarded_location: self
3628-
.environment
3629-
.discarded_names
3630-
.get(&eco_format!("_{name}"))
3631-
.cloned(),
3632-
type_with_name_in_scope: self
3633-
.environment
3634-
.module_types
3635-
.keys()
3636-
.any(|typ| typ == name),
3637-
imported_modules_with_same_public_variable_name: self
3638-
.environment
3639-
.imported_modules
3640-
.iter()
3641-
.filter_map(|(module_name, (_, module))| {
3642-
module.get_public_value(name).map(|_| module_name)
3643-
})
3644-
.cloned()
3645-
.collect_vec(),
3646-
},
3642+
None => {
3643+
let imported_modules_with_same_public_variable_name = match var_usage {
3644+
// This is a function call, we need to suggest a public
3645+
// variable which is a function with the correct arity
3646+
VarUsage::Call { arity } => self
3647+
.environment
3648+
.imported_modules
3649+
.iter()
3650+
.filter_map(|(module_name, (_, module))| {
3651+
module
3652+
.get_public_value(name)
3653+
.filter(|value_constructor| {
3654+
match value_constructor.type_.fn_types() {
3655+
Some((fn_arguments_type, _)) => {
3656+
fn_arguments_type.len() == arity
3657+
}
3658+
None => false,
3659+
}
3660+
})
3661+
.map(|_| module_name)
3662+
})
3663+
.cloned()
3664+
.collect_vec(),
3665+
// This is a reference to a variable, we need to suggest
3666+
// public variables of any type
3667+
VarUsage::Other => self
3668+
.environment
3669+
.imported_modules
3670+
.iter()
3671+
.filter_map(|(module_name, (_, module))| {
3672+
module.get_public_value(name).map(|_| module_name)
3673+
})
3674+
.cloned()
3675+
.collect_vec(),
3676+
};
3677+
3678+
Error::UnknownVariable {
3679+
location: *location,
3680+
name: name.clone(),
3681+
variables: self.environment.local_value_names(),
3682+
discarded_location: self
3683+
.environment
3684+
.discarded_names
3685+
.get(&eco_format!("_{name}"))
3686+
.cloned(),
3687+
type_with_name_in_scope: self
3688+
.environment
3689+
.module_types
3690+
.keys()
3691+
.any(|typ| typ == name),
3692+
imported_modules_with_same_public_variable_name,
3693+
}
3694+
}
36473695
}
36483696
}
36493697

@@ -3701,7 +3749,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
37013749
..
37023750
} if arguments.is_empty() => {
37033751
// Type check the record constructor
3704-
let constructor = self.infer_value_constructor(&module, &name, &location)?;
3752+
let constructor =
3753+
self.infer_value_constructor(&module, &name, &location, VarUsage::Other)?;
37053754

37063755
let (tag, field_map) = match &constructor.variant {
37073756
ValueConstructorVariant::Record {
@@ -3740,7 +3789,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
37403789
// field_map, is always None here because untyped not yet unified
37413790
..
37423791
} => {
3743-
let constructor = self.infer_value_constructor(&module, &name, &location)?;
3792+
let constructor = self.infer_value_constructor(
3793+
&module,
3794+
&name,
3795+
&location,
3796+
VarUsage::Call {
3797+
arity: arguments.len(),
3798+
},
3799+
)?;
37443800

37453801
let (tag, field_map, variant_index) = match &constructor.variant {
37463802
ValueConstructorVariant::Record {
@@ -3882,7 +3938,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
38823938
..
38833939
} => {
38843940
// Infer the type of this constant
3885-
let constructor = self.infer_value_constructor(&module, &name, &location)?;
3941+
let constructor =
3942+
self.infer_value_constructor(&module, &name, &location, VarUsage::Other)?;
38863943
match constructor.variant {
38873944
ValueConstructorVariant::ModuleConstant { .. }
38883945
| ValueConstructorVariant::LocalConstant { .. }
@@ -4059,6 +4116,30 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
40594116
kind: CallKind,
40604117
) -> (TypedExpr, Vec<TypedCallArg>, Arc<Type>) {
40614118
let fun = match fun {
4119+
UntypedExpr::Var { location, name } => {
4120+
// Because we are not calling infer / infer_or_error directly,
4121+
// we do not warn if there was a previous panic. Check for that
4122+
// here. Not perfect, but works.
4123+
if self.previous_panics {
4124+
self.warn_for_unreachable_code(location, PanicPosition::PreviousExpression);
4125+
}
4126+
4127+
match self.infer_var(
4128+
name,
4129+
location,
4130+
VarUsage::Call {
4131+
arity: arguments.len(),
4132+
},
4133+
ReferenceRegistration::RegisterReferences,
4134+
) {
4135+
Ok(typed_expr) => typed_expr,
4136+
Err(error) => {
4137+
self.problems.error(error);
4138+
self.error_expr(location)
4139+
}
4140+
}
4141+
}
4142+
40624143
UntypedExpr::FieldAccess {
40634144
label,
40644145
container,

0 commit comments

Comments
 (0)