gleam-lang / gleam

⭐️ A friendly language for building type-safe, scalable systems!
https://gleam.run
Apache License 2.0
16.68k stars 698 forks source link

Compiler crash when checking exhaustiveness against duplicate type constructors #3272

Closed nijolas closed 4 days ago

nijolas commented 1 month ago

Getting a compiler crash via the LSP consistently:

Panic: compiler-core\src\exhaustiveness.rs:746
    Custom type variants must exist: Type { name: "FrameType", hint: AlternativeTypes(["Bool", "UtfCodepoint", "Int", "Nil", "Game", "Error", "Frame", "BitArray", "Float", "Result", "FrameType", "List", "String"]) }
Gleam version: 1.2.1
Operating system: windows

Here is the code file that's causing it:

import gleam/int
import gleam/list

pub opaque type Frame {
  Frame(rolls: List(Int), bonus: List(Int))
}

pub type Game {
  Game(frames: List(Frame))
}

pub type Error {
  InvalidPinCount
  GameComplete
  GameNotComplete
  EmptyFrame
  BadFrame
}

type FrameType {
  Incomplete
  OpenFrame
  Spare
  Strike
  BadFrame
}

pub fn roll(game: Game, knocked_pins: Int) -> Result(Game, Error) {
  case game.frames {
    // first roll
    [] -> Ok(game |> push_frame(new_frame(knocked_pins)))
    // later roll
    [frame, ..rest] -> {
      // check for incomplete frame
      case frame.rolls {
        [] -> Error(EmptyFrame)
        // only one roll has occurred
        [roll] -> Ok(game |> push_frame(frame |> push_roll(knocked_pins)))
        // the previous frame is finished
        [roll1, roll2] -> {
          // see what we rolled
          case frame_type(frame) {
            Spare -> {
              // spare adds the next roll's bonus
              let final_frame = frame |> add_bonus(knocked_pins)
              Ok(Game([new_frame(knocked_pins), final_frame, ..rest]))
            }
            _ -> Ok(game |> push_frame(new_frame(knocked_pins)))
          }
        }
        _ -> Error(BadFrame)
      }
    }
  }
}

pub fn score(game: Game) -> Result(Int, Error) {
  let result =
    game.frames
    |> list.map(fn(f) { f.rolls })
    |> list.flatten
    |> int.sum
  Ok(result)
}

fn new_frame(knocked_pins: Int) -> Frame {
  Frame([knocked_pins], [])
}

fn push_roll(f: Frame, knocked_pins: Int) -> Frame {
  Frame([knocked_pins, ..f.rolls], f.bonus)
}

fn add_bonus(f: Frame, bonus: Int) -> Frame {
  Frame(f.rolls, [bonus, ..f.bonus])
}

fn push_frame(g: Game, f: Frame) -> Game {
  Game([f, ..g.frames])
}

fn frame_type(f: Frame) -> FrameType {
  case f.rolls {
    [] -> Incomplete
    [a] -> {
      case a {
        _ if a < 10 -> Incomplete
        _ if a == 10 -> Strike
        _ -> BadFrame
      }
    }
    [a, b] -> {
      case a + b {
        c if c < 10 -> OpenFrame
        c if c == 10 -> Spare
        _ -> BadFrame
      }
    }
    _ -> BadFrame
  }
}

I'm part way through working on the Bowling exercise on Exercism. As I was working, VSCode threw up an error saying:

The Gleam Language Server server crashed 5 times in the last 3 minutes. The server will not be restarted. See the output for more information.

And that's what I found in the output. I restarted VSCode a few times, and this code seems to be triggering it consistently.

This is not an issue for me of course! But I thought it might help toward improving tooling and compiler errors 🙏

nijolas commented 1 month ago

Looks like the issue might be that I've declared BadFrame twice - once as an Error and once as a FrameType.

nijolas commented 1 month ago

I'm struggling to find a smaller reproduction, but I can confirm that changing FrameType.BadFrame to just FrameType.Bad makes the error disappear.

Changing it back to BadFrame immediately triggers the crash again.

nijolas commented 1 month ago

Possibly related to #3256

lpil commented 4 weeks ago

Hello! Yes I think this is a bug introduced by our fault tolerant analysis, and the exhaustiveness checker failing to handle code that is invalid, while previously it would always be valid. I think it may be the same as the other issue.

Acepie commented 4 days ago

This can be closed/has been resolved right?