Skip to content

Commit

Permalink
Support circular and out-of-order trait/type references (#47)
Browse files Browse the repository at this point in the history
Long overdue. This allows types and traits to be defined in any order **within a package** (circular type dependencies will never be allowed outside of a package because Crochet's capabilities require a proper hierarchy of dependencies), and can finally get rid of the awkward `0-types.crochet` convention. Types and traits can now be defined wherever makes most sense for them to be, and circular constraints now work properly. 

This fixes #10.
  • Loading branch information
robotlolita committed Jan 9, 2022
1 parent db80c25 commit d233998
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 16 deletions.
2 changes: 1 addition & 1 deletion source/compiler/lower-to-ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ export class LowerToIR {

HasTrait: (pos, value, trait) => {
const id = this.context.register(pos);

debugger;
return [
...this.expression(value),
new IR.TraitTest(id, this.trait(trait)),
Expand Down
3 changes: 3 additions & 0 deletions source/crochet/crochet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ export class BootedCrochet {
for (const x of pkg.sources) {
await this.load_source(x, pkg, cpkg);
}

VM.Types.verify_package_types(cpkg);
VM.Types.verify_package_traits(cpkg);
}

private reify_capability_grants(pkg: Package.ResolvedPackage) {
Expand Down
9 changes: 7 additions & 2 deletions source/generated/crochet-grammar.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions source/grammar/crochet.lingua
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,8 @@ grammar Crochet : Program {
-> Expression.Invoke(meta, Signature.Binary(meta, op, s, t))
| s:invokePrePost is_ t:typeAppPrimary
-> Expression.HasType(meta, s, t)
| s:invokePrePost has_ t:traitName
-> Expression.HasTrait(meta, s, t)
| invokePrePost

castType =
Expand Down
26 changes: 24 additions & 2 deletions source/vm/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export function load_module(
for (const x of program.declarations) {
last_doc = load_declaration(universe, module, x, last_doc) ?? null;
}
Types.promote_missing_types(module);

return module;
}
Expand Down Expand Up @@ -504,7 +505,7 @@ export function load_declaration(
module,
declaration.parent
).type;
const type = new CrochetType(
const new_type = new CrochetType(
module,
declaration.name,
declaration.documentation,
Expand All @@ -516,6 +517,19 @@ export function load_declaration(
false,
declaration.meta
);
let type;
const missing = Types.try_get_placeholder_type(module, declaration.name);
if (missing != null) {
type = Types.fulfill_placeholder_type(
module,
missing,
new_type,
declaration.visibility
);
} else {
type = new_type;
}

parent.sub_types.push(type);

Types.define_type(module, declaration.name, type, declaration.visibility);
Expand Down Expand Up @@ -675,12 +689,20 @@ export function load_declaration(
}

case t.TRAIT: {
const trait = new CrochetTrait(
const new_trait = new CrochetTrait(
module,
declaration.name,
declaration.documentation,
declaration.meta
);
let trait;
const missing = Types.try_get_placeholder_trait(module, declaration.name);
if (missing != null) {
trait = Types.fulfill_placeholder_trait(module, missing, new_trait);
} else {
trait = new_trait;
}

Types.define_trait(module, declaration.name, trait);
break;
}
Expand Down
1 change: 1 addition & 0 deletions source/vm/evaluation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,7 @@ export class Thread {
Values.has_trait(trait, value)
);
this.push(activation, result);
activation.next();
return _continue;
}

Expand Down
6 changes: 6 additions & 0 deletions source/vm/intrinsics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ export class CrochetWorld {
}

export class CrochetPackage {
readonly missing_traits: Namespace<CrochetTrait>;
readonly missing_types: Namespace<CrochetType>;
readonly types: PassthroughNamespace<CrochetType>;
readonly traits: PassthroughNamespace<CrochetTrait>;
readonly definitions: PassthroughNamespace<CrochetValue>;
Expand All @@ -378,6 +380,8 @@ export class CrochetPackage {
readonly name: string,
readonly filename: string
) {
this.missing_traits = new Namespace(null, null, null);
this.missing_types = new Namespace(null, null, null);
this.types = new PassthroughNamespace(world.types, name);
this.traits = new PassthroughNamespace(world.traits, name);
this.definitions = new PassthroughNamespace(world.definitions, name);
Expand All @@ -390,6 +394,7 @@ export class CrochetPackage {
}

export class CrochetModule {
readonly missing_types: Namespace<CrochetType>;
readonly types: Namespace<CrochetType>;
readonly definitions: Namespace<CrochetValue>;
readonly relations: Namespace<CrochetRelation>;
Expand All @@ -405,6 +410,7 @@ export class CrochetModule {
) {
this.open_prefixes = new Set();
this.open_prefixes.add("crochet.core");
this.missing_types = new Namespace(pkg.missing_types, null, null);
this.types = new Namespace(pkg.types, pkg.name, this.open_prefixes);
this.definitions = new Namespace(
pkg.definitions,
Expand Down
13 changes: 13 additions & 0 deletions source/vm/namespaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export class Namespace<V> {
this.allowed_prefixes = allowed_prefixes || new Set<string>();
}

get own_bindings(): Map<string, V> {
return this._bindings;
}

get bindings(): Map<string, V> {
const result = new Map<string, V>();

Expand Down Expand Up @@ -39,6 +43,15 @@ export class Namespace<V> {
}
}

remove(name: string): boolean {
if (this.has_own(name)) {
this._bindings.delete(name);
return true;
} else {
return false;
}
}

define(name: string, value: V): boolean {
if (this.has_own(name)) {
return false;
Expand Down
146 changes: 136 additions & 10 deletions source/vm/primitives/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CrochetPackage } from "..";
import * as IR from "../../ir";
import { unreachable } from "../../utils/utils";
import { ErrArbitrary } from "../errors";
Expand Down Expand Up @@ -72,24 +73,32 @@ export function get_type(module: CrochetModule, name: string) {
const value = module.types.try_lookup(name);
if (value != null) {
return value;
} else {
throw new ErrArbitrary(
"no-type",
`No type ${name} is accessible from ${Location.module_location(module)}`
);
}

const missing = module.missing_types.try_lookup(name);
if (missing != null) {
return missing;
}

const placeholder = make_placeholder_type(module, name);
module.missing_types.define(name, placeholder);
return placeholder;
}

export function get_trait(module: CrochetModule, name: string) {
const value = module.traits.try_lookup(name);
if (value != null) {
return value;
} else {
throw new ErrArbitrary(
"no-trait",
`No trait ${name} is accessible from ${Location.module_location(module)}`
);
}

const missing = module.pkg.missing_traits.try_lookup(name);
if (missing != null) {
return missing;
}

const placeholder = make_placeholder_trait(module, name);
module.pkg.missing_traits.define(name, placeholder);
return placeholder;
}

export function get_type_namespaced(
Expand Down Expand Up @@ -357,3 +366,120 @@ export function resolve_field_layout_with_base(
}
return results;
}

export function make_placeholder_type(module: CrochetModule, name: string) {
return new CrochetType(
module,
name,
"(placeholder type)",
null,
[],
[],
false,
null
);
}

export function make_placeholder_trait(module: CrochetModule, name: string) {
return new CrochetTrait(module, name, "(placeholder trait)", null);
}

export function try_get_placeholder_type(module: CrochetModule, name: string) {
return module.missing_types.try_lookup(name);
}

export function try_get_placeholder_trait(module: CrochetModule, name: string) {
return module.pkg.missing_traits.try_lookup(name);
}

export function fulfill_placeholder_type(
module: CrochetModule,
placeholder: CrochetType,
type: CrochetType,
visibility: IR.Visibility
) {
const value = module.missing_types.try_lookup(type.name);
if (value !== placeholder) {
throw new ErrArbitrary("internal", `Invalid placeholder for ${type.name}.`);
}

module.missing_types.remove(type.name);
if (visibility === IR.Visibility.GLOBAL) {
module.pkg.missing_types.remove(type.name);
}

const p: { -readonly [k in keyof typeof type]: typeof type[k] } = placeholder;
p.sealed = type.sealed;
p.layout = type.layout;
p.sub_types = type.sub_types;
p.traits = type.traits;
p.protected_by = type.protected_by;
p.module = type.module;
p.name = type.name;
p.documentation = type.documentation;
p.parent = type.parent;
p.fields = type.fields;
p.types = type.types;
p.is_static = type.is_static;
p.meta = type.meta;

return placeholder;
}

export function fulfill_placeholder_trait(
module: CrochetModule,
placeholder: CrochetTrait,
trait: CrochetTrait
) {
const value = module.pkg.missing_traits.try_lookup(trait.name);
if (value !== placeholder) {
throw new ErrArbitrary("internal", `Invalid placeholder for ${trait.name}`);
}

module.pkg.missing_traits.remove(trait.name);
const p: { -readonly [k in keyof typeof trait]: typeof trait[k] } =
placeholder;
p.implemented_by = trait.implemented_by;
p.protected_by = trait.protected_by;
p.module = trait.module;
p.name = trait.name;
p.documentation = trait.documentation;
p.meta = trait.meta;

return placeholder;
}

export function promote_missing_types(module: CrochetModule) {
for (const [name, type] of module.missing_types.own_bindings) {
module.missing_types.remove(name);
if (!module.pkg.missing_types.define(name, type)) {
throw new ErrArbitrary("internal", `Duplicate placeholder ${name}`);
}
}
}

export function verify_package_types(pkg: CrochetPackage) {
if (pkg.missing_types.own_bindings.size > 0) {
throw new ErrArbitrary(
"internal",
`Package ${
pkg.name
} cannot be loaded because it's missing the following type definitions: ${[
...pkg.missing_types.own_bindings.keys(),
].join(", ")}`
);
}
}

export function verify_package_traits(pkg: CrochetPackage) {
if (pkg.missing_traits.own_bindings.size > 0) {
throw new ErrArbitrary(
"internal",
`Package ${
pkg.name
} cannot be loaded because it's missing the following trait definitions: ${[
...pkg.missing_types.own_bindings.keys(),
].join(", ")}`
);
}
}
5 changes: 4 additions & 1 deletion tests/vm-tests/crochet.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
"crochet.time"
],
"sources": [
"types-before.crochet",
"types.crochet",
"types-after.crochet",
"for-all.crochet",
"interpolation.crochet",
"multimethod.crochet",
"search.crochet",
"branch.crochet",
"actions.crochet",
"effects.crochet",
"dsl.crochet"
"dsl.crochet",
"traits.crochet"
],
"capabilities": {
"requires": ["crochet.debug/internal"],
Expand Down
36 changes: 36 additions & 0 deletions tests/vm-tests/traits.crochet
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
% crochet

trait test-trait-a with
command Type check;
end

local singleton a;
local singleton b;
implement test-trait-a for a;
command a check = true;
command b check = true;
command (W has test-trait-a) check-trait-a = W check;

test "Trait dispatch works" do
assert a check === true;
assert a check-trait-a === true;
end

test "Trait assertions work" do
assert a has test-trait-a;
assert not (b has test-trait-a);
end

implement test-trait-b for b;
command (W has test-trait-b) check-trait-b = W check;

trait test-trait-b with
command T check-trait-b;
end

test "Traits can be defined after constraints" do
assert b check === true;
assert b check-trait-b === true;
assert b has test-trait-b;
assert not (b has test-trait-a);
end
3 changes: 3 additions & 0 deletions tests/vm-tests/types-after.crochet
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
% crochet

type placeholder-type-after;
3 changes: 3 additions & 0 deletions tests/vm-tests/types-before.crochet
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
% crochet

type placeholder-type-before(value is future-type1);
Loading

0 comments on commit d233998

Please sign in to comment.