Module: Rigor::Effects::Narrowing
- Defined in:
- lib/rigor/effects/narrowing.rb
Overview
Argument-dependent narrowing for catalogue rows (ADR-103 WD3; the row's narrow: names one of
these handlers, and data/effects/core.yml says why each row wants one).
A handler reads the call's own argument literals and nothing else. There is no dataflow here on purpose: the effect scan is observational (it never asks the typer for anything it did not already decide), and a narrowing that depended on inference would make the catalogue's answer a function of analysis quality rather than of the source in front of it.
Every handler is total and answers an upper bound: when the literal does not settle the
question it returns the row's parent label, never a guess. open(path) with a computed argument
is io — file, pipe or URI — and that is the honest reading of the classic pipe-injection shape.
Handlers are module functions dispatched by a case, deliberately not a Hash of lambdas: the
catalogue is loaded once per process and inherited across the fork pool, and a table of procs is
neither Marshal-clean nor shareable.
Constant Summary collapse
- IO_ANY =
LabelSet.new(["io"]).freeze
- FS =
LabelSet.new(["io.fs"]).freeze
- FS_READ =
LabelSet.new(["io.fs.read"]).freeze
- FS_WRITE =
LabelSet.new(["io.fs.write"]).freeze
- FS_READ_WRITE =
LabelSet.new(["io.fs.read", "io.fs.write"]).freeze
- PROCESS =
LabelSet.new(["io.process"]).freeze
- HTTP =
LabelSet.new(["io.net.http"]).freeze
- TIME =
LabelSet.new(["nondet.time"]).freeze
- DB =
LabelSet.new(["io.db"]).freeze
- DB_READ =
LabelSet.new(["io.db.read"]).freeze
- DB_WRITE =
LabelSet.new(["io.db.write"]).freeze
- DB_TRANSACTION =
LabelSet.new(["io.db.transaction"]).freeze
- RANDOM =
LabelSet.new(["nondet.random"]).freeze
- NONE =
LabelSet::EMPTY
- HANDLERS =
%w[kernel_open file_open pathname_open time_new random_new uri_open sql_verb].freeze
Class Method Summary collapse
-
.apply(name, node) ⇒ Object
The labels
name's handler reads offnode. -
.file_open(node) ⇒ Object
File.open(path, mode)— the mode literal decides the direction. -
.kernel_open(node) ⇒ Object
Kernel#open— a path, a|commandpipe, or (with open-uri loaded) a URI. - .known?(name) ⇒ Boolean
-
.pathname_open(node) ⇒ Object
Pathname#open(mode)— the same reading one argument to the left, because the receiver is the path. -
.random_new(node) ⇒ Object
Random.new— a seed argument makes the generator reproducible from the source; without one it draws the seed from platform entropy. -
.time_new(node) ⇒ Object
Time.new— with no positional arguments it isTime.now; with any it constructs from them and consults no clock. -
.uri_open(node) ⇒ Object
URI.open/OpenURI.open_uri— the scheme literal decides the subsystem.
Class Method Details
.apply(name, node) ⇒ Object
The labels name's handler reads off node. An unknown handler name answers nil, which the
catalogue loader rejects at load time rather than at call time.
65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rigor/effects/narrowing.rb', line 65 def apply(name, node) case name when "kernel_open" then kernel_open(node) when "file_open" then file_open(node) when "pathname_open" then pathname_open(node) when "time_new" then time_new(node) when "random_new" then random_new(node) when "uri_open" then uri_open(node) when "sql_verb" then sql_verb(node) end end |
.file_open(node) ⇒ Object
File.open(path, mode) — the mode literal decides the direction. An ABSENT mode is not an
unknown one: Ruby's default is "r", so it reads. A mode the call computes — or an integer flag
such as File::RDWR, which the scan deliberately does not resolve — is genuinely unknown and
answers the subsystem parent.
113 114 115 |
# File 'lib/rigor/effects/narrowing.rb', line 113 def file_open(node) mode_labels(node, 1) end |
.kernel_open(node) ⇒ Object
Kernel#open — a path, a |command pipe, or (with open-uri loaded) a URI. A literal leading
| is the pipe form; anything else literal is a path, whose direction the mode literal decides.
101 102 103 104 105 106 107 |
# File 'lib/rigor/effects/narrowing.rb', line 101 def kernel_open(node) target = literal_prefix(argument(node, 0)) return IO_ANY if target.nil? return PROCESS if target.start_with?("|") mode_labels(node, 1) end |
.known?(name) ⇒ Boolean
59 60 61 |
# File 'lib/rigor/effects/narrowing.rb', line 59 def known?(name) HANDLERS.include?(name) end |
.pathname_open(node) ⇒ Object
Pathname#open(mode) — the same reading one argument to the left, because the receiver is the
path.
119 120 121 |
# File 'lib/rigor/effects/narrowing.rb', line 119 def pathname_open(node) mode_labels(node, 0) end |
.random_new(node) ⇒ Object
Random.new — a seed argument makes the generator reproducible from the source; without one it
draws the seed from platform entropy.
131 132 133 |
# File 'lib/rigor/effects/narrowing.rb', line 131 def random_new(node) positional_count(node).zero? ? RANDOM : NONE end |
.time_new(node) ⇒ Object
Time.new — with no positional arguments it is Time.now; with any it constructs from them and
consults no clock. Keyword arguments (in:) do not count: Time.new(in: "+09:00") is still now.
125 126 127 |
# File 'lib/rigor/effects/narrowing.rb', line 125 def time_new(node) positional_count(node).zero? ? TIME : NONE end |
.uri_open(node) ⇒ Object
URI.open / OpenURI.open_uri — the scheme literal decides the subsystem. A bare path (no
scheme://) is open-uri's filesystem fallback; a scheme nobody rowed answers the parent.
137 138 139 140 141 142 143 144 145 |
# File 'lib/rigor/effects/narrowing.rb', line 137 def uri_open(node) target = literal_prefix(argument(node, 0)) return IO_ANY if target.nil? return HTTP if target.start_with?("http://", "https://") return FS_READ if target.start_with?("file://") return IO_ANY if target.include?("://") FS_READ end |