autotype 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +93 -0
- data/Rakefile +21 -0
- data/autotype.example.yml +19 -0
- data/autotype.gemspec +38 -0
- data/exe/autotype +6 -0
- data/ext/autotype/Makefile +273 -0
- data/ext/autotype/autotype.bundle.dSYM/Contents/Info.plist +20 -0
- data/ext/autotype/autotype.bundle.dSYM/Contents/Resources/Relocations/aarch64/autotype.bundle.yml +5 -0
- data/ext/autotype/autotype.c +277 -0
- data/ext/autotype/extconf.rb +15 -0
- data/lib/autotype/discovery_profile.rb +527 -0
- data/lib/autotype/engine.rb +4613 -0
- data/lib/autotype/native.rb +34 -0
- data/lib/autotype/native_bridge.rb +121 -0
- data/lib/autotype/profile.rb +46 -0
- data/lib/autotype/type_string.rb +48 -0
- data/lib/autotype/version.rb +5 -0
- data/lib/autotype.rb +15 -0
- data/native/autotype/Makefile +23 -0
- data/native/autotype/include/stc.h +107 -0
- data/native/autotype/src/main.c +50 -0
- data/native/autotype/src/solver.c +296 -0
- data/native/autotype/src/type.c +223 -0
- data/spec/autotype_config_spec.rb +91 -0
- data/spec/autotype_native_spec.rb +71 -0
- data/spec/autotype_spec.rb +1028 -0
- data/spec/examples.txt +72 -0
- data/spec/fixtures/autotype.yml +9 -0
- data/spec/fixtures/entities/tool_call.rb +7 -0
- data/spec/fixtures/entities/tool_result.rb +7 -0
- data/spec/fixtures/pipeline/actors/demo_actor.rb +8 -0
- data/spec/spec_helper.rb +25 -0
- metadata +149 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AutotypeNative
|
|
4
|
+
class << self
|
|
5
|
+
def available?
|
|
6
|
+
load_extension!
|
|
7
|
+
const_get(:Solver)
|
|
8
|
+
true
|
|
9
|
+
rescue LoadError, StandardError
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def load_extension!
|
|
14
|
+
return if @extension_loaded
|
|
15
|
+
|
|
16
|
+
ext_dir = File.expand_path("../../ext/autotype", __dir__)
|
|
17
|
+
bundle = File.join(ext_dir, "autotype.#{RbConfig::CONFIG['DLEXT']}")
|
|
18
|
+
native_sources = Dir[File.expand_path("../../native/autotype/src/*.c", __dir__)]
|
|
19
|
+
extension_source = File.join(ext_dir, "autotype.c")
|
|
20
|
+
sources = native_sources + [extension_source]
|
|
21
|
+
|
|
22
|
+
stale =
|
|
23
|
+
!File.exist?(bundle) ||
|
|
24
|
+
sources.any? { |source| File.exist?(source) && File.mtime(source) > File.mtime(bundle) }
|
|
25
|
+
|
|
26
|
+
if stale
|
|
27
|
+
Dir.chdir(ext_dir) { system("ruby extconf.rb && make", exception: true) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
require bundle
|
|
31
|
+
@extension_loaded = true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Autotype
|
|
4
|
+
module NativeBridge
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def enabled?
|
|
8
|
+
ENV["AUTOTYPE_NATIVE"] == "1" && native_available?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def native_available?
|
|
12
|
+
require_relative "native"
|
|
13
|
+
AutotypeNative.available?
|
|
14
|
+
rescue LoadError, StandardError
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def export_type(type)
|
|
19
|
+
case type
|
|
20
|
+
when Autotype::Named
|
|
21
|
+
{ "k" => "named", "name" => type.name }
|
|
22
|
+
when Autotype::Generic
|
|
23
|
+
{ "k" => "generic", "name" => type.name, "args" => type.arguments.map { export_type(_1) } }
|
|
24
|
+
when Autotype::Union
|
|
25
|
+
{ "k" => "union", "members" => type.members.map { export_type(_1) } }
|
|
26
|
+
when Autotype::TypeVariable
|
|
27
|
+
{ "k" => "var", "id" => type.object_id, "hint" => type.hint.to_s }
|
|
28
|
+
else
|
|
29
|
+
{ "k" => "named", "name" => "Object" }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def import_type(hash)
|
|
34
|
+
entry = hash.transform_keys(&:to_s)
|
|
35
|
+
case entry["k"]
|
|
36
|
+
when "named" then Autotype::Named.new(entry["name"])
|
|
37
|
+
when "generic"
|
|
38
|
+
Autotype::Generic.new(entry["name"], entry["args"].map { import_type(_1) })
|
|
39
|
+
when "union"
|
|
40
|
+
Autotype::Union.new(entry["members"].map { import_type(_1) })
|
|
41
|
+
else
|
|
42
|
+
Autotype::Named.new("Object")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def export_graph(inferencer)
|
|
47
|
+
resolve = inferencer.method(:resolve)
|
|
48
|
+
var_objects = {}
|
|
49
|
+
capabilities = []
|
|
50
|
+
|
|
51
|
+
inferencer.instance_variable_get(:@methods).each do |method|
|
|
52
|
+
method.capabilities.each do |capability|
|
|
53
|
+
next if capability.arguments.any? { |argument| argument.is_a?(Autotype::Function) }
|
|
54
|
+
|
|
55
|
+
args = capability.arguments.reject { |argument| argument.is_a?(Autotype::Function) }
|
|
56
|
+
result = capability.result
|
|
57
|
+
next unless result.is_a?(Autotype::TypeVariable)
|
|
58
|
+
|
|
59
|
+
var_objects[result.object_id] = result
|
|
60
|
+
receiver = capability.receiver
|
|
61
|
+
var_objects[receiver.object_id] = receiver if receiver.is_a?(Autotype::TypeVariable)
|
|
62
|
+
|
|
63
|
+
args.each do |argument|
|
|
64
|
+
var_objects[argument.object_id] = argument if argument.is_a?(Autotype::TypeVariable)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
capabilities << {
|
|
68
|
+
"message" => capability.message.to_s,
|
|
69
|
+
"receiver" => export_type(receiver),
|
|
70
|
+
"result" => result.object_id,
|
|
71
|
+
"args" => args.map { export_type(_1) },
|
|
72
|
+
"line" => capability.line
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
bindings = inferencer.instance_variable_get(:@substitutions).each_with_object({}) do |(variable, type), hash|
|
|
78
|
+
resolved = resolve.call(type)
|
|
79
|
+
next if resolved.is_a?(Autotype::TypeVariable)
|
|
80
|
+
|
|
81
|
+
hash[variable.object_id] = export_type(resolved)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
"capabilities" => capabilities,
|
|
86
|
+
"bindings" => bindings,
|
|
87
|
+
"var_count" => var_objects.size
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_result!(inferencer, result)
|
|
92
|
+
result = result.transform_keys(&:to_s) if result.is_a?(Hash)
|
|
93
|
+
var_map = {}
|
|
94
|
+
inferencer.instance_variable_get(:@methods).each do |method|
|
|
95
|
+
method.capabilities.each do |capability|
|
|
96
|
+
[capability.receiver, capability.result, *capability.arguments].each do |node|
|
|
97
|
+
var_map[node.object_id] = node if node.is_a?(Autotype::TypeVariable)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
method.parameters.each { |parameter| var_map[parameter.type.object_id] = parameter.type if parameter.type.is_a?(Autotype::TypeVariable) }
|
|
101
|
+
method.ivars.each_value { |type| var_map[type.object_id] = type if type.is_a?(Autotype::TypeVariable) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
subs = inferencer.instance_variable_get(:@substitutions)
|
|
105
|
+
result.fetch("bindings").each do |object_id, type_hash|
|
|
106
|
+
variable = var_map[object_id.to_i]
|
|
107
|
+
next unless variable
|
|
108
|
+
|
|
109
|
+
subs[variable] = import_type(type_hash)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def prime!(inferencer)
|
|
114
|
+
native_available?
|
|
115
|
+
graph = export_graph(inferencer)
|
|
116
|
+
result = AutotypeNative.solve_graph(graph)
|
|
117
|
+
apply_result!(inferencer, result)
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Autotype
|
|
4
|
+
class << self
|
|
5
|
+
def configuration
|
|
6
|
+
@configuration ||= Configuration.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def configure
|
|
10
|
+
yield(configuration) if block_given?
|
|
11
|
+
configuration
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def profile
|
|
15
|
+
configure.load_profile
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def reset_configuration!
|
|
19
|
+
@configuration = nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Configuration
|
|
24
|
+
attr_accessor :config_path
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@config_path = ENV["AUTOTYPE_CONFIG"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def skipped_files
|
|
31
|
+
load_profile.skipped_files
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def load_profile
|
|
35
|
+
return @profile if defined?(@profile) && @profile
|
|
36
|
+
|
|
37
|
+
path = config_path || DiscoveryProfile.find
|
|
38
|
+
@profile = path ? DiscoveryProfile.load_overrides(path) : DiscoveryProfile.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reload_profile!
|
|
42
|
+
remove_instance_variable(:@profile) if defined?(@profile)
|
|
43
|
+
load_profile
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Autotype
|
|
4
|
+
module TypeString
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def parse(string)
|
|
8
|
+
string = string.to_s.strip
|
|
9
|
+
raise ArgumentError, "type string cannot be empty" if string.empty?
|
|
10
|
+
|
|
11
|
+
if string.include?("|")
|
|
12
|
+
members = string.split("|").map { parse(_1.strip) }
|
|
13
|
+
return members.first if members.length == 1
|
|
14
|
+
|
|
15
|
+
return Union.new(members.uniq)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if (match = string.match(/\A([A-Za-z_][\w:]*)\[(.*)\]\z/m))
|
|
19
|
+
name = match[1]
|
|
20
|
+
args = split_args(match[2]).map { parse(_1) }
|
|
21
|
+
return Generic.new(name, args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Named.new(string)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def split_args(source)
|
|
28
|
+
args = []
|
|
29
|
+
depth = 0
|
|
30
|
+
start = 0
|
|
31
|
+
source.each_char.with_index do |char, index|
|
|
32
|
+
case char
|
|
33
|
+
when "["
|
|
34
|
+
depth += 1
|
|
35
|
+
when "]"
|
|
36
|
+
depth -= 1
|
|
37
|
+
when ","
|
|
38
|
+
if depth.zero?
|
|
39
|
+
args << source[start...index]
|
|
40
|
+
start = index + 1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
args << source[start..]
|
|
45
|
+
args.map(&:strip).reject(&:empty?)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/autotype.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "autotype/version"
|
|
4
|
+
require_relative "autotype/type_string"
|
|
5
|
+
require_relative "autotype/profile"
|
|
6
|
+
require_relative "autotype/discovery_profile"
|
|
7
|
+
require_relative "autotype/engine"
|
|
8
|
+
|
|
9
|
+
module Autotype
|
|
10
|
+
class << self
|
|
11
|
+
def run(argv = ARGV)
|
|
12
|
+
CLI.run(argv)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
CC ?= cc
|
|
2
|
+
CFLAGS ?= -std=c11 -O3 -Wall -Wextra -Iinclude
|
|
3
|
+
LDFLAGS ?=
|
|
4
|
+
|
|
5
|
+
SRCS = src/type.c src/solver.c src/main.c
|
|
6
|
+
OBJS = $(SRCS:.c=.o)
|
|
7
|
+
BIN = stc_solve
|
|
8
|
+
|
|
9
|
+
.PHONY: all clean test
|
|
10
|
+
|
|
11
|
+
all: $(BIN)
|
|
12
|
+
|
|
13
|
+
$(BIN): $(OBJS)
|
|
14
|
+
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
15
|
+
|
|
16
|
+
src/%.o: src/%.c include/stc.h
|
|
17
|
+
$(CC) $(CFLAGS) -c -o $@ $<
|
|
18
|
+
|
|
19
|
+
clean:
|
|
20
|
+
rm -f $(OBJS) $(BIN)
|
|
21
|
+
|
|
22
|
+
test: $(BIN)
|
|
23
|
+
./$(BIN)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#ifndef STC_H
|
|
2
|
+
#define STC_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
|
|
8
|
+
#define STC_MAX_NAME 256
|
|
9
|
+
#define STC_MAX_ITERATIONS 64
|
|
10
|
+
|
|
11
|
+
typedef uint64_t stc_var_id;
|
|
12
|
+
|
|
13
|
+
typedef enum {
|
|
14
|
+
STC_TY_NIL = 0,
|
|
15
|
+
STC_TY_NAMED,
|
|
16
|
+
STC_TY_VAR,
|
|
17
|
+
STC_TY_GENERIC,
|
|
18
|
+
STC_TY_UNION
|
|
19
|
+
} stc_type_kind;
|
|
20
|
+
|
|
21
|
+
typedef struct stc_type stc_type;
|
|
22
|
+
|
|
23
|
+
struct stc_type {
|
|
24
|
+
stc_type_kind kind;
|
|
25
|
+
union {
|
|
26
|
+
struct { char name[STC_MAX_NAME]; } named;
|
|
27
|
+
struct { stc_var_id id; char hint[64]; } var;
|
|
28
|
+
struct {
|
|
29
|
+
char name[32];
|
|
30
|
+
stc_type **args;
|
|
31
|
+
size_t arg_count;
|
|
32
|
+
} generic;
|
|
33
|
+
struct {
|
|
34
|
+
stc_type **members;
|
|
35
|
+
size_t member_count;
|
|
36
|
+
} union_;
|
|
37
|
+
} as;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
typedef struct {
|
|
41
|
+
stc_var_id id;
|
|
42
|
+
char hint[64];
|
|
43
|
+
} stc_var;
|
|
44
|
+
|
|
45
|
+
typedef struct {
|
|
46
|
+
stc_type *receiver;
|
|
47
|
+
stc_type *result;
|
|
48
|
+
char message[64];
|
|
49
|
+
stc_type **args;
|
|
50
|
+
size_t arg_count;
|
|
51
|
+
int32_t line;
|
|
52
|
+
} stc_capability;
|
|
53
|
+
|
|
54
|
+
typedef struct {
|
|
55
|
+
char name[STC_MAX_NAME];
|
|
56
|
+
char owner[STC_MAX_NAME];
|
|
57
|
+
stc_type *result;
|
|
58
|
+
stc_capability *capabilities;
|
|
59
|
+
size_t capability_count;
|
|
60
|
+
} stc_method;
|
|
61
|
+
|
|
62
|
+
typedef struct {
|
|
63
|
+
stc_var_id var_id;
|
|
64
|
+
stc_type *type;
|
|
65
|
+
} stc_substitution;
|
|
66
|
+
|
|
67
|
+
typedef struct {
|
|
68
|
+
stc_var *vars;
|
|
69
|
+
size_t var_count;
|
|
70
|
+
|
|
71
|
+
stc_method *methods;
|
|
72
|
+
size_t method_count;
|
|
73
|
+
|
|
74
|
+
stc_substitution *subs;
|
|
75
|
+
size_t sub_count;
|
|
76
|
+
size_t sub_cap;
|
|
77
|
+
|
|
78
|
+
stc_type **type_pool;
|
|
79
|
+
size_t type_pool_count;
|
|
80
|
+
size_t type_pool_cap;
|
|
81
|
+
|
|
82
|
+
int32_t changed;
|
|
83
|
+
int32_t iterations;
|
|
84
|
+
int32_t converged;
|
|
85
|
+
} stc_solver;
|
|
86
|
+
|
|
87
|
+
stc_solver *stc_solver_new(void);
|
|
88
|
+
void stc_solver_free(stc_solver *solver);
|
|
89
|
+
|
|
90
|
+
stc_type *stc_type_named(stc_solver *solver, const char *name);
|
|
91
|
+
stc_type *stc_type_var(stc_solver *solver, stc_var_id id, const char *hint);
|
|
92
|
+
stc_type *stc_type_generic(stc_solver *solver, const char *name, stc_type **args, size_t arg_count);
|
|
93
|
+
stc_type *stc_type_union(stc_solver *solver, stc_type **members, size_t member_count);
|
|
94
|
+
|
|
95
|
+
stc_type *stc_dereference(stc_solver *solver, stc_type *type);
|
|
96
|
+
stc_type *stc_resolve(stc_solver *solver, stc_type *type);
|
|
97
|
+
|
|
98
|
+
void stc_unify(stc_solver *solver, stc_type *left, stc_type *right);
|
|
99
|
+
void stc_bind(stc_solver *solver, stc_var_id var_id, stc_type *type);
|
|
100
|
+
|
|
101
|
+
void stc_apply_builtin(stc_solver *solver, stc_capability *capability);
|
|
102
|
+
void stc_solve_to_fixed_point(stc_solver *solver);
|
|
103
|
+
int stc_solver_run(stc_solver *solver);
|
|
104
|
+
|
|
105
|
+
void stc_solver_load_flat(stc_solver *solver, stc_capability *capabilities, size_t capability_count);
|
|
106
|
+
|
|
107
|
+
#endif
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#include "../include/stc.h"
|
|
2
|
+
|
|
3
|
+
#include <stdio.h>
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
|
|
7
|
+
/* Minimal smoke-test driver for the native solver core. */
|
|
8
|
+
int main(void) {
|
|
9
|
+
stc_solver *solver = stc_solver_new();
|
|
10
|
+
if (!solver) return 1;
|
|
11
|
+
|
|
12
|
+
stc_capability *caps = calloc(2, sizeof(stc_capability));
|
|
13
|
+
if (!caps) return 1;
|
|
14
|
+
|
|
15
|
+
caps[0].receiver = stc_type_var(solver, 1, "hash");
|
|
16
|
+
caps[0].result = stc_type_var(solver, 2, "array");
|
|
17
|
+
strncpy(caps[0].message, "[]", sizeof(caps[0].message) - 1);
|
|
18
|
+
caps[0].args = malloc(sizeof(stc_type *));
|
|
19
|
+
caps[0].args[0] = stc_type_var(solver, 3, "key");
|
|
20
|
+
caps[0].arg_count = 1;
|
|
21
|
+
|
|
22
|
+
caps[1].receiver = stc_type_var(solver, 2, "array");
|
|
23
|
+
caps[1].result = stc_type_var(solver, 10, "result");
|
|
24
|
+
strncpy(caps[1].message, "<<", sizeof(caps[1].message) - 1);
|
|
25
|
+
caps[1].args = malloc(sizeof(stc_type *));
|
|
26
|
+
caps[1].args[0] = stc_type_var(solver, 4, "item");
|
|
27
|
+
caps[1].arg_count = 1;
|
|
28
|
+
|
|
29
|
+
stc_solver_load_flat(solver, caps, 2);
|
|
30
|
+
|
|
31
|
+
stc_type *key = stc_type_named(solver, "Integer");
|
|
32
|
+
stc_type *element = stc_type_var(solver, 99, "element");
|
|
33
|
+
stc_type *hash_args[] = { key, stc_type_generic(solver, "Array", &element, 1) };
|
|
34
|
+
stc_type *hash_type = stc_type_generic(solver, "Hash", hash_args, 2);
|
|
35
|
+
stc_bind(solver, 1, hash_type);
|
|
36
|
+
|
|
37
|
+
stc_type *item = stc_type_named(solver, "Widget");
|
|
38
|
+
stc_bind(solver, 4, item);
|
|
39
|
+
|
|
40
|
+
stc_solver_run(solver);
|
|
41
|
+
|
|
42
|
+
stc_type *resolved = stc_resolve(solver, stc_type_var(solver, 10, "result"));
|
|
43
|
+
printf("converged=%d iterations=%d\n", solver->converged, solver->iterations);
|
|
44
|
+
if (resolved && resolved->kind == STC_TY_GENERIC) {
|
|
45
|
+
printf("result=%s\n", resolved->as.generic.name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stc_solver_free(solver);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|