Shopify / Shopify/tapioca

Sorbet generates unreachable assignment for AR default attributes

Open
#1,370 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

help-wanted
Dominant language
Ruby
Stars
873
Forks
164
Avg merge
4d 27m
Merged PRs (30d)
9

Description

Referred from https://github.com/sorbet/sorbet/issues/6621

Input

Model:

# typed: strict

class Shortcode < ApplicationRecord
  after_initialize :set_defaults, if: :new_record?

  private

  def set_defaults
    self.code ||= SecureRandom.urlsafe_base64(10)
  end
end

# == Schema Information
#
# Table name: shortcodes
#
#  id            :bigint           not null, primary key
#  code          :string           not null
#  created_at    :datetime         not null
#  updated_at    :datetime         not null
#
# Indexes
#
#  index_shortcodes_on_code  (code) UNIQUE

Migration:

class CreateShortcodes < ActiveRecord::Migration[7.0]
  def change
    create_table :shortcodes do |t|
      t.string :code, null: false
      t.index :code, unique: true
      t.timestamps
    end
  end
end

Annotation RBI (generated by tapioca):

class Shortcode
  include GeneratedAttributeMethods
  extend GeneratedRelationMethods

  private

  # cut for length

  module GeneratedAttributeMethods

  # cut for length

    sig { returns(::String) }
    def code; end

    sig { params(value: ::String).returns(::String) }
    def code=(value); end

    sig { returns(T::Boolean) }
    def code?; end

    sig { returns(T.nilable(::String)) }
    def code_before_last_save; end

    sig { returns(T.untyped) }
    def code_before_type_cast; end

    sig { returns(T::Boolean) }
    def code_came_from_user?; end

    sig { returns(T.nilable([::String, ::String])) }
    def code_change; end

    sig { returns(T.nilable([::String, ::String])) }
    def code_change_to_be_saved; end

    sig { returns(T::Boolean) }
    def code_changed?; end

    sig { returns(T.nilable(::String)) }
    def code_in_database; end

    sig { returns(T.nilable([::String, ::String])) }
    def code_previous_change; end

    sig { returns(T::Boolean) }
    def code_previously_changed?; end

    sig { returns(T.nilable(::String)) }
    def code_previously_was; end

    sig { returns(T.nilable(::String)) }
    def code_was; end

    sig { void }
    def code_will_change!; end
  end
end
Observed output
$ bundle exec srb tc
Indexing |=======================================================| ETA: 0h00m00s
Resolving |======================================================| ETA: 0h00m01s
app/models/shortcode.rb:7: This code is unreachable https://srb.help/700600m00s
    7 |    self.code ||= SecureRandom.urlsafe_base64(10)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    app/models/micropage.rb:7: This condition was always truthy (String)
    7 |    self.code ||= SecureRandom.urlsafe_base64(10)
            ^^^^^^^^^^^^^^^
  Got String originating from:
    app/models/shortcode.rb:7:
    7 |    self.short_code ||= SecureRandom.urlsafe_base64(10)
            ^^^^^^^^^^^^^^^
CFG+Inference |==================================================| ETA: 0h00m00s
Errors: 1
error Command failed with exit code 1.
Expected behavior

The check should have passed, because it is possible for self.short_code to be either nil or defined depending on how Shortcode.create! is called. You can call Shortcode.create!(code: "my_code") and create a record with code defined, and you can also call Shortcode.create! at which point line 7 above will assign a random code. The code is not unreachable.


Sorbet version: 0.5.10160
Tapioca version: 0.10.3

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the diagnostic with the Shortcode example in app/models/shortcode.rb by running bundle exec srb tc, then inspect the generated Tapioca RBI declarations shown in the issue. The issue is done when a valid Active Record default assignment no longer produces an unreachable-code error while the example still type-checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.