Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject extension scopes while running the resolution algorithm #1170

Merged
merged 17 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions crates/metaslang/bindings/generated/public_api.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 108 additions & 9 deletions crates/metaslang/bindings/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
//! To do this, add a `source_node` attribute, whose value is a syntax node capture:
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! node def
//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
//! }
Expand Down Expand Up @@ -161,7 +161,7 @@
//! `syntax_type` attribute, whose value is a string indicating the syntax type.
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! node def
//! ; ...
//! attr (def) syntax_type = "function"
Expand All @@ -175,7 +175,7 @@
//! `definiens_node` attribute, whose value is a syntax node that spans the definiens.
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ... @body [FunctionBody] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]] @body [FunctionBody]] {
//! node def
//! ; ...
//! attr (def) definiens_node = @body
Expand All @@ -189,7 +189,7 @@
//! To connect two stack graph nodes, use the `edge` statement to add an edge between them:
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! node def
//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
//! node body
Expand All @@ -201,7 +201,7 @@
//! you can add a `precedence` attribute to each edge to indicate which paths are prioritized:
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! node def
//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
//! node body
Expand All @@ -220,7 +220,7 @@
//! ``` skip
//! global ROOT_NODE
//!
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! node def
//! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition
//! edge ROOT_NODE -> def
Expand All @@ -235,14 +235,80 @@
//! a scope node with a kind as follows:
//!
//! ``` skip
//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] {
//! @func [FunctionDefinition [FunctionName @id [Identifier]]] {
//! ; ...
//! node param_scope
//! attr (param_scope) debug_kind = "param_scope"
//! ; ...
//! }
//! ```
//!
//! ### Other node attributes introduced in Slang's usage of stack-graphs
//!
//! #### `tag` attribute
//!
//! This is used to attach a specific meaning to the node, to alter the ranking
//! algorithm used when attempting to disambiguate between multiple definitions
//! found for a reference. This is an optional string attribute.
//!
//! Possible values:
//!
//! - "alias": marks a definition node as a semantic alias of another definition
//! (eg. an import alias)
//!
//! - "c3": used to mark a function/method definition to be a candidate in
//! disambiguation using the C3 linearisation algorithm. In order for C3
//! linearisation to be possible, type hierarchy attributes need to be provided
//! as well (see `parents` attribute below).
//!
//! - "super": marks a reference as a call to super virtual call. This modifies
//! the C3 linearisation algorithm by eliminating the candidates that are at
//! or further down the hierarchy of where the reference occurs. To determine
//! where the reference occurs, we also use the `parents` attribute.
//!
//! #### `parents` attribute
//!
//! Is used to convey semantic hierarchy. Can be applied to both definitions and
//! references. It's an optional, list of graph nodes attribute.
//!
//! For references it can indicate in which language context the reference
//! occurs (eg. in which method or class). For definitions it can indicate the
//! enclosing type of the definition, or parent classes in a class hierarchy.
//! The parent handles themselves can refer to definitions or references. In the
//! later case, generally speaking they will need to be resolved at resolution
//! time in order to be useful.
//!
//! #### `export_node` and `import_nodes`
//!
//! These are used to define static fixed edges to add via `set_context()`.
//! Using `set_context()` will modify the underlying stack graph by inserting
//! edges from the `import_nodes` of all parents (resolved recursively) of the
//! given context, to the `export_node` associated with the context.
//!
//! This can be used to inject virtual method implementations defined in
//! subclasses in the scope of their parent classes, which are otherwise
//! lexically inaccessible.
//!
//! `export_node` is an optional graph node attribute, and `import_nodes` is an
//! optional list of graph nodes. Both apply only to definition nodes.
//!
//! #### `extension_hook`, `extension_scope` and `inherit_extensions`
//!
//! These attributes enable the bindings API to resolve extension methods by
//! injecting specific scopes at potentially unrelated (lexically speaking)
//! nodes in the stack graph. Availability and application of extension scopes
//! depend on the call site (ie. the reference node). Thus, the extension scope
//! to (potentially) apply when resolving a reference is computed by looking up
//! the `parents` of the reference and then querying those parent nodes for
//! their `extension_scope` (an optional scope node). Any extension providing
//! node can also have the `inherit_extensions` attribute (a boolean) which
//! indicates that the algorithm should recurse and resolve its parents to
//! further look for other extensions scopes.
//!
//! Finally, the attribute `extension_hook` defines where in the graph should
//! these extension scopes be injected. This is typically the root lexical
//! scope. This attribute applies to any scope node and is boolean.
//!

mod cancellation;
mod functions;
Expand Down Expand Up @@ -282,6 +348,9 @@ static IS_DEFINITION_ATTR: &str = "is_definition";
static IS_ENDPOINT_ATTR: &str = "is_endpoint";
static IS_EXPORTED_ATTR: &str = "is_exported";
static IS_REFERENCE_ATTR: &str = "is_reference";
static EXTENSION_HOOK_ATTR: &str = "extension_hook";
static EXTENSION_SCOPE_ATTR: &str = "extension_scope";
static INHERIT_EXTENSIONS_ATTR: &str = "inherit_extensions";
static PARENTS_ATTR: &str = "parents";
static SCOPE_ATTR: &str = "scope";
static SOURCE_NODE_ATTR: &str = "source_node";
Expand All @@ -302,6 +371,8 @@ static POP_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
EXPORT_NODE_ATTR,
IMPORT_NODES_ATTR,
SYNTAX_TYPE_ATTR,
EXTENSION_SCOPE_ATTR,
INHERIT_EXTENSIONS_ATTR,
])
});
static POP_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
Expand All @@ -315,6 +386,8 @@ static POP_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
EXPORT_NODE_ATTR,
IMPORT_NODES_ATTR,
SYNTAX_TYPE_ATTR,
EXTENSION_SCOPE_ATTR,
INHERIT_EXTENSIONS_ATTR,
])
});
static PUSH_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
Expand All @@ -336,8 +409,14 @@ static PUSH_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
PARENTS_ATTR,
])
});
static SCOPE_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, IS_EXPORTED_ATTR, IS_ENDPOINT_ATTR]));
static SCOPE_ATTRS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
HashSet::from([
TYPE_ATTR,
IS_EXPORTED_ATTR,
IS_ENDPOINT_ATTR,
EXTENSION_HOOK_ATTR,
])
});

// Edge attribute names
static PRECEDENCE_ATTR: &str = "precedence";
Expand All @@ -362,6 +441,7 @@ pub(crate) struct Builder<'a, KT: KindTypes + 'static> {
cursors: HashMap<Handle<Node>, Cursor<KT>>,
definitions_info: HashMap<Handle<Node>, DefinitionBindingInfo<KT>>,
references_info: HashMap<Handle<Node>, ReferenceBindingInfo>,
extension_hooks: HashSet<Handle<Node>>,
}

pub(crate) struct BuildResult<KT: KindTypes + 'static> {
Expand All @@ -370,6 +450,8 @@ pub(crate) struct BuildResult<KT: KindTypes + 'static> {
pub cursors: HashMap<Handle<Node>, Cursor<KT>>,
pub definitions_info: HashMap<Handle<Node>, DefinitionBindingInfo<KT>>,
pub references_info: HashMap<Handle<Node>, ReferenceBindingInfo>,
// Nodes where we want to inject extensions
pub extension_hooks: HashSet<Handle<Node>>,
}

impl<'a, KT: KindTypes + 'static> Builder<'a, KT> {
Expand All @@ -392,6 +474,7 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> {
cursors: HashMap::new(),
definitions_info: HashMap::new(),
references_info: HashMap::new(),
extension_hooks: HashSet::new(),
}
}

Expand Down Expand Up @@ -480,6 +563,7 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> {
cursors: self.cursors,
definitions_info: self.definitions_info,
references_info: self.references_info,
extension_hooks: self.extension_hooks,
})
}

Expand Down Expand Up @@ -896,6 +980,15 @@ impl<'a, KT: KindTypes> Builder<'a, KT> {
None => Vec::new(),
};

let extension_scope = match node.attributes.get(EXTENSION_SCOPE_ATTR) {
Some(extension_scope) => {
Some(self.node_handle_for_graph_node(extension_scope.as_graph_node_ref()?))
}
None => None,
};

let inherit_extensions = Self::load_flag(node, INHERIT_EXTENSIONS_ATTR)?;

self.definitions_info.insert(
node_handle,
DefinitionBindingInfo {
Expand All @@ -904,13 +997,19 @@ impl<'a, KT: KindTypes> Builder<'a, KT> {
parents,
export_node,
import_nodes,
extension_scope,
inherit_extensions,
},
);
} else if stack_graph_node.is_reference() {
self.references_info
.insert(node_handle, ReferenceBindingInfo { tag, parents });
}

if Self::load_flag(node, EXTENSION_HOOK_ATTR)? {
self.extension_hooks.insert(node_handle);
}

Ok(())
}

Expand Down
48 changes: 36 additions & 12 deletions crates/metaslang/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use metaslang_cst::cursor::Cursor;
use metaslang_cst::kinds::KindTypes;
use metaslang_graph_builder::ast::File;
use metaslang_graph_builder::functions::Functions;
use resolver::Resolver;
use resolver::{ResolveOptions, Resolver};
use semver::Version;
use stack_graphs::graph::StackGraph;

Expand All @@ -32,10 +32,10 @@ pub(crate) struct DefinitionBindingInfo<KT: KindTypes + 'static> {
definiens: Option<Cursor<KT>>,
tag: Option<Tag>,
parents: Vec<GraphHandle>,
#[allow(dead_code)]
export_node: Option<GraphHandle>,
#[allow(dead_code)]
import_nodes: Vec<GraphHandle>,
extension_scope: Option<GraphHandle>,
inherit_extensions: bool,
}

pub(crate) struct ReferenceBindingInfo {
Expand All @@ -53,6 +53,7 @@ pub struct Bindings<KT: KindTypes + 'static> {
cursor_to_definitions: HashMap<CursorID, GraphHandle>,
cursor_to_references: HashMap<CursorID, GraphHandle>,
context: Option<GraphHandle>,
extension_hooks: HashSet<GraphHandle>,
}

pub enum FileDescriptor {
Expand Down Expand Up @@ -136,6 +137,7 @@ impl<KT: KindTypes + 'static> Bindings<KT> {
cursor_to_definitions: HashMap::new(),
cursor_to_references: HashMap::new(),
context: None,
extension_hooks: HashSet::new(),
}
}

Expand Down Expand Up @@ -187,6 +189,7 @@ impl<KT: KindTypes + 'static> Bindings<KT> {
self.definitions_info
.extend(result.definitions_info.drain());
self.references_info.extend(result.references_info.drain());
self.extension_hooks.extend(result.extension_hooks.drain());

result
}
Expand Down Expand Up @@ -258,18 +261,13 @@ impl<KT: KindTypes + 'static> Bindings<KT> {
// cannot be resolved at this point?
self.to_reference(*handle)
.unwrap()
.jump_to_definition()
.non_recursive_resolve()
.ok()
}
})
.collect()
}

pub fn lookup_definition_by_name(&self, name: &str) -> Option<Definition<'_, KT>> {
self.all_definitions()
.find(|definition| definition.get_cursor().unwrap().node().unparse() == name)
}

pub fn get_context(&self) -> Option<Definition<'_, KT>> {
self.context.and_then(|handle| self.to_definition(handle))
}
Expand Down Expand Up @@ -338,6 +336,10 @@ impl<KT: KindTypes + 'static> Bindings<KT> {
}
results
}

pub(crate) fn is_extension_hook(&self, node_handle: GraphHandle) -> bool {
self.extension_hooks.contains(&node_handle)
}
}

struct DisplayCursor<'a, KT: KindTypes + 'static> {
Expand Down Expand Up @@ -401,6 +403,20 @@ impl<'a, KT: KindTypes + 'static> Definition<'a, KT> {
.unwrap_or_default()
}

pub(crate) fn get_extension_scope(&self) -> Option<GraphHandle> {
self.owner
.definitions_info
.get(&self.handle)
.and_then(|info| info.extension_scope)
}

pub(crate) fn inherit_extensions(&self) -> bool {
self.owner
.definitions_info
.get(&self.handle)
.map_or(false, |info| info.inherit_extensions)
}

pub fn to_handle(self) -> DefinitionHandle {
DefinitionHandle(self.handle)
}
Expand Down Expand Up @@ -471,12 +487,20 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> {
.expect("Reference does not have a valid file descriptor")
}

pub fn jump_to_definition(&self) -> Result<Definition<'a, KT>, ResolutionError<'a, KT>> {
Resolver::build_for(self).first()
pub fn resolve_definition(&self) -> Result<Definition<'a, KT>, ResolutionError<'a, KT>> {
OmarTawfik marked this conversation as resolved.
Show resolved Hide resolved
Resolver::build_for(self, ResolveOptions::Full).first()
}

pub fn definitions(&self) -> Vec<Definition<'a, KT>> {
Resolver::build_for(self).all()
Resolver::build_for(self, ResolveOptions::Full).all()
}

pub(crate) fn non_recursive_resolve(
&self,
) -> Result<Definition<'a, KT>, ResolutionError<'a, KT>> {
// This was likely originated from a full resolution call, so cut
// recursion here by restricting the resolution algorithm.
Resolver::build_for(self, ResolveOptions::NonRecursive).first()
}

pub(crate) fn has_tag(&self, tag: Tag) -> bool {
Expand Down
Loading
Loading