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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Autotype
4
+ VERSION = "0.1.0"
5
+ 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
+ }