Skip to content

Conversation

abs0luty
Copy link
Contributor

@abs0luty abs0luty commented Oct 2, 2025

Short summary of issue

When a record field definition refers to an unknown type, the errors you get when using that record always claim that record field doesn't exist, including claiming the record has one less field:

pub type Wibble {
  Wibble(x: Int, f: fn(Wobble) -> Int)
}

pub fn wibble() {
  Wibble(1, fn(_) { 2 })
}

pub fn wobble(wibble: Wibble) {
  wibble.f
}

pub fn woo(wibble: Wibble) {
  Wibble(..wibble, x: 1)
}

results in:

  │
2 │   Wibble(x: Int, f: fn(Wobble) -> Int)
  │                        ^^^^^^ Did you mean `Wibble`?

The type `Wobble` is not defined or imported in this module.

error: Incorrect arity
  ┌─ /src/main.gleam:6:3
  │
6 │   Wibble(1, fn(_) { 2 })
  │   ^^^^^^^^^^^^^^^^^^^^^^ Expected 1 argument, got 2


error: Unknown record field
   ┌─ /src/main.gleam:10:10
   │
10 │   wibble.f
   │          ^ Did you mean `x`?

The value being accessed has this type:

    Wibble

It has these accessible fields:

    .x

warning: Redundant record update
   ┌─ /src/main.gleam:14:3
   │
14 │   Wibble(..wibble, x: 1)
   │   ^^^^^^^^^^^^^^^^^^^^^^ This record update specifies all fields

Hint: It is better style to use the record creation syntax.

Solution

Ideally, compiler should not ignore a record field where type checking failed. So a solution is to report the error, but put unbound type variable, instead of skipping the field. This practice is already used in other parts of analyzer. For instance:

let arguments_types = arguments
.iter()
.map(|argument| {
match hydrator.type_from_option_ast(
&argument.annotation,
environment,
&mut self.problems,
) {
Ok(type_) => type_,
Err(error) => {
self.problems.error(error);
environment.new_unbound_var()
}
}
})
.collect();
let return_type =
match hydrator.type_from_option_ast(return_annotation, environment, &mut self.problems)
{
Ok(type_) => type_,
Err(error) => {
self.problems.error(error);
environment.new_unbound_var()
}
};

This is a code for processing function arguments, analyzer puts unbound variable as placeholder for invalid type.

Output after fix:

error: Unknown type
  ┌─ /src/one/two.gleam:2:24
  │
2 │   Wibble(x: Int, f: fn(Wobble) -> Int)
  │                        ^^^^^^ Did you mean `Wibble`?

The type `Wobble` is not defined or imported in this module.

This PR also affects test for exhaustiveness, specifically:

---
source: compiler-core/src/type_/tests/exhaustiveness.rs
---

type Wibble {
    One(Int)
    Two(Absent)
}

pub fn main(wibble) {
    case wibble {
        One(x) -> x
    }
}


error: Unknown type
  ┌─ /src/one/two.gleam:4:9
  │
4 │     Two(Absent)
  │         ^^^^^^

The type `Absent` is not defined or imported in this module.

error: Private type used in public interface
  ┌─ /src/one/two.gleam:7:1
  │
7 │ pub fn main(wibble) {
  │ ^^^^^^^^^^^^^^^^^^^

The following type is private, but is being used by this public export.

    Wibble

Private types can only be used within the module that defines them.

error: Inexhaustive patterns
   ┌─ /src/one/two.gleam:8:5
   │  
 8 │ ╭     case wibble {
 9 │ │         One(x) -> x
10 │ │     }
   │ ╰─────^

This case expression does not have a pattern for all possible values. If it
is run on one of the values without a pattern then it will crash.

The missing patterns are:

-   Two
+   Two(_)

Which I believe is ok and is actually also relevant in relation to the issue. Before the fix, the record field Absent was skipped, now it isn't.

@abs0luty abs0luty changed the title Fix #4931 Don't forget record fields where type checking failed. Oct 2, 2025
@abs0luty abs0luty changed the title Don't forget record fields where type checking failed. Don't forget record fields where type checking failed Oct 2, 2025
@abs0luty abs0luty force-pushed the fix/4931 branch 4 times, most recently from 848448c to 54920d6 Compare October 3, 2025 12:21
@abs0luty abs0luty changed the title Don't forget record fields where type checking failed Fix confusing error when a record field does not typecheck Oct 3, 2025
@abs0luty abs0luty changed the title Fix confusing error when a record field does not typecheck Make analyzer more resilient to record fields where type checking failed Oct 3, 2025
@abs0luty abs0luty changed the title Make analyzer more resilient to record fields where type checking failed Make analyzer resilient to record fields where type checking failed Oct 3, 2025
@abs0luty abs0luty changed the title Make analyzer resilient to record fields where type checking failed Make analyzer resilient to type checking errors in record fields Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confusing error when a record field does not typecheck

1 participant