Skip to content

Commit

Permalink
Merge pull request #1716 from vinistock/vs/add_type_templates
Browse files Browse the repository at this point in the history
Add type templates for RBS and RBI
  • Loading branch information
kddnewton authored Oct 27, 2023
2 parents ed1cb4a + 759ab31 commit d12cf95
Show file tree
Hide file tree
Showing 12 changed files with 595 additions and 29 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ jobs:
env:
LANG: "C"

check_annotations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
bundler-cache: true
- name: Check annotations
run: |
bundle exec rake compile
bundle exec rake check_annotations
build:
strategy:
fail-fast: false
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ a.out
/src/serialize.c
/src/token_type.c
/src/**/*.o
/sig/prism.rbs
/rbi/prism.rbi

compile_commands.json
.cache/
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ gem "ffi", platform: %i[mri mswin mingw x64_mingw]
group :memcheck do
gem "ruby_memcheck", platform: %i[mri mswin mingw x64_mingw]
end
gem "rbs", platform: %i[mri mswin mingw x64_mingw]
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ GEM
rake (13.0.6)
rake-compiler (1.2.5)
rake
rbs (3.2.2)
ruby_memcheck (2.0.1)
nokogiri
test-unit (3.6.1)
Expand All @@ -31,6 +32,7 @@ DEPENDENCIES
prism!
rake
rake-compiler
rbs
ruby_memcheck
test-unit

Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ task make_no_debug: [:templates] do
end

# decorate the gem build task with prerequisites
task build: [:templates, :check_manifest]
task build: [:templates, :check_annotations, :check_manifest]

# the C extension
task "compile:prism" => ["templates"] # must be before the ExtensionTask is created
Expand Down
4 changes: 4 additions & 0 deletions prism.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ Gem::Specification.new do |spec|
"src/util/pm_strpbrk.c",
"src/prism.c",
"prism.gemspec",
"sig/prism.rbs",
"sig/prism_static.rbs",
"rbi/prism.rbi",
"rbi/prism_static.rbi"
]

spec.extensions = ["ext/prism/extconf.rb"]
Expand Down
26 changes: 26 additions & 0 deletions rakelib/check_annotations.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

desc "Check that RBS and RBI generated files are valid"
task :check_annotations do
require "prism"
require "rbs"
require "rbs/cli"

# Run `rbs` against the generated file, which checks for valid syntax and any missing constants
puts "Checking RBS annotations"
# output = Bundler.with_original_env { `rbs -I sig/prism.rbs validate` }
begin
cli = RBS::CLI.new(stdout: STDOUT, stderr: STDERR)
cli.run(["-I", "sig", "validate"])
rescue => e
abort(e.message)
end

# For RBI files, we just use Prism itself to check for valid syntax since they use Ruby compliant syntax
puts "Checking RBI annotations"
result = Prism.parse_file("rbi/prism.rbi")
abort(result.errors.map(&:inspect).join("\n")) unless result.success?

result = Prism.parse_file("rbi/prism_static.rbi")
abort(result.errors.map(&:inspect).join("\n")) unless result.success?
end
182 changes: 182 additions & 0 deletions rbi/prism_static.rbi
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# typed: true

module Prism
class ParseResult
sig { returns(ProgramNode) }
def value; end

sig { returns(T::Array[Comment]) }
def comments; end

sig { returns(T::Array[ParseError]) }
def errors; end

sig { returns(T::Array[ParseWarning]) }
def warnings; end

sig { returns(Source) }
def source; end
end

class ParseError
sig { returns(String) }
def message; end

sig { returns(Location) }
def location; end
end

class ParseWarning
sig { returns(String) }
def message; end

sig { returns(Location) }
def location; end
end

class Node
sig { returns(T::Array[T.nilable(Node)]) }
def child_nodes; end

sig { returns(Location) }
def location; end

sig { returns(String) }
def slice; end
end

class Comment
sig { returns(Location) }
def location; end
end

class Location
sig { params(source: Source, start_offset: Integer, length: Integer).void }
def initialize(source, start_offset, length); end

sig { returns(String) }
def slice; end

sig { returns(T::Array[Comment]) }
def comments; end

sig { params(options: T.untyped).returns(Location) }
def copy(**options); end

sig { returns(Integer) }
def start_offset; end

sig { returns(Integer) }
def end_offset; end

sig { returns(Integer) }
def start_line; end

sig { returns(Integer) }
def end_line; end

sig { returns(Integer) }
def start_column; end

sig { returns(Integer) }
def end_column; end
end

class Source
sig { params(source: String, offsets: T::Array[Integer]).void }
def initialize(source, offsets); end

sig { params(offset: Integer, length: Integer).returns(String) }
def slice(offset, length); end

sig { params(value: Integer).returns(Integer) }
def line(value); end

sig { params(value: Integer).returns(Integer) }
def line_offset(value); end

sig { params(value: Integer).returns(Integer) }
def column(value); end

sig { returns(String) }
def source; end

sig { returns(T::Array[Integer]) }
def offsets; end
end

class Token
sig { params(type: T.untyped, value: String, location: Location).void }
def initialize(type, value, location); end

sig { params(keys: T.untyped).returns(T.untyped) }
def deconstruct_keys(keys); end

sig { params(q: T.untyped).returns(T.untyped) }
def pretty_print(q); end

sig { params(other: T.untyped).returns(T::Boolean) }
def ==(other); end

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

sig { returns(String) }
def value; end

sig { returns(Location) }
def location; end
end

class NodeInspector
sig { params(prefix: String).void }
def initialize(prefix); end

sig { returns(String) }
def prefix; end

sig { returns(String) }
def output; end

# Appends a line to the output with the current prefix.
sig { params(line: String).void }
def <<(line); end

# This generates a string that is used as the header of the inspect output
# for any given node.
sig { params(node: Node).returns(String) }
def header(node); end

# Generates a string that represents a list of nodes. It handles properly
# using the box drawing characters to make the output look nice.
sig { params(prefix: String, nodes: T::Array[Node]).returns(String) }
def list(prefix, nodes); end

# Generates a string that represents a location field on a node.
sig { params(value: Location).returns(String) }
def location(value); end

# Generates a string that represents a child node.
sig { params(node: Node, append: String).returns(String) }
def child_node(node, append); end

# Returns a new inspector that can be used to inspect a child node.
sig { params(append: String).returns(NodeInspector) }
def child_inspector(append); end

# Returns the output as a string.
sig { returns(String) }
def to_str; end
end

class BasicVisitor
sig { params(node: T.nilable(Node)).void }
def visit(node); end

sig { params(nodes: T::Array[T.nilable(Node)]).void }
def visit_all(nodes); end

sig { params(node: Node).void }
def visit_child_nodes(node); end
end
end
110 changes: 110 additions & 0 deletions sig/prism_static.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
module Prism
class ParseResult
def value: () -> ProgramNode
def comments: () -> Array[Comment]
def errors: () -> Array[ParseError]
def warnings: () -> Array[ParseWarning]
def source: () -> Source
end

class ParseError
def message: () -> String
def location: () -> Location
end

class ParseWarning
def message: () -> String
def location: () -> Location
end

class Node
def child_nodes: () -> Array[Node?]
def location: () -> Location
def slice: () -> String
end

class Comment
def location: () -> Location
end

class Location
def initialize: (source: Source, start_offset: Integer, length: Integer) -> void
def slice: () -> String
def comments: () -> Array[Comment]
def copy: (**untyped) -> Location
def start_offset: () -> Integer
def end_offset: () -> Integer
def start_line: () -> Integer
def end_line: () -> Integer
def start_column: () -> Integer
def end_column: () -> Integer
end

class Source
attr_reader source: String
attr_reader offsets: Array[Integer]

@source: String
@offsets: Array[Integer]

def initialize: (source: String, offsets: Array[Integer]) -> void
def slice: (offset: Integer, length: Integer) -> String
def line: (value: Integer) -> Integer
def line_offset: (value: Integer) -> Integer
def column: (value: Integer) -> Integer
end

class Token
attr_reader type: untyped
attr_reader value: String
attr_reader location: Location

@type: untyped
@value: String
@location: Location

def initialize: (type: untyped, value: String, location: Location) -> void
def deconstruct_keys: (keys: untyped) -> untyped
def pretty_print: (q: untyped) -> untyped
def ==: (other: untyped) -> bool
end

class NodeInspector
attr_reader prefix: String
attr_reader output: String

@prefix: String
@output: String

def initialize: (prefix: String) -> void

# Appends a line to the output with the current prefix.
def <<: (line: String) -> void

# This generates a string that is used as the header of the inspect output
# for any given node.
def header: (node: Node) -> String

# Generates a string that represents a list of nodes. It handles properly
# using the box drawing characters to make the output look nice.
def list: (prefix: String, nodes: Array[Node]) -> String

# Generates a string that represents a location field on a node.
def location: (value: Location) -> String

# Generates a string that represents a child node.
def child_node: (node: Node, append: String) -> String

# Returns a new inspector that can be used to inspect a child node.
def child_inspector: (append: String) -> NodeInspector

# Returns the output as a string.
def to_str: () -> String
end

class BasicVisitor
def visit: (node: Node?) -> void
def visit_all: (nodes: Array[Node?]) -> void
def visit_child_nodes: (node: Node) -> void
end
end
Loading

0 comments on commit d12cf95

Please sign in to comment.