Skip to content

Commit e909fb4

Browse files
committed
feat: tracing of InferType impls & Unifier::unify
Debugging type checking code in VSCode is suboptimal - we really want a graphical representation of inference at every step (but also a debugger that doesn't lock up at inconvenient times would be great). To make instrumentation consistent I implemented a `#[trace_infer]` attribute macro which can be used on `InferType` impls. The `Unifier` has a single `#[tracing::instrument!(..)]` call and there is also code in `EqlMapper` to dump nodes types and type substitutions at the end of the type checking process which is useful when debugging failing inference tests. To get a trace from a test, uncomment the `init_tracing();` line. The output is *very* verbose but very useful.
1 parent 4f638fe commit e909fb4

File tree

19 files changed

+373
-22
lines changed

19 files changed

+373
-22
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "eql-mapper-macros"
3+
version.workspace = true
4+
edition.workspace = true
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
syn = { version = "2.0", features = ["full"] }
11+
quote = "1.0"
12+
proc-macro2 = "1.0"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use proc_macro::TokenStream;
2+
use quote::{quote, ToTokens};
3+
use syn::{
4+
parse::Parse, parse_macro_input, parse_quote, Attribute, FnArg, Ident, ImplItem, ImplItemFn,
5+
ItemImpl, Pat, PatType, Signature, Type, TypePath, TypeReference,
6+
};
7+
8+
/// This macro generates consistently defined `#[tracing::instrument]` attributes for `InferType::infer_enter` &
9+
/// `InferType::infer_enter` implementations on `TypeInferencer`.
10+
///
11+
/// This attribute MUST be defined on the trait `impl` itself (not the trait method impls).
12+
#[proc_macro_attribute]
13+
pub fn trace_infer(_attr: TokenStream, item: TokenStream) -> TokenStream {
14+
let mut input = parse_macro_input!(item as ItemImpl);
15+
16+
for item in &mut input.items {
17+
if let ImplItem::Fn(ImplItemFn {
18+
attrs,
19+
sig:
20+
Signature {
21+
ident: method,
22+
inputs,
23+
..
24+
},
25+
..
26+
}) = item
27+
{
28+
let node_ident_and_type: Option<(&Ident, &Type)> =
29+
if let Some(FnArg::Typed(PatType {
30+
ty: node_ty, pat, ..
31+
})) = inputs.get(1)
32+
{
33+
if let Pat::Ident(pat_ident) = &**pat {
34+
Some((&pat_ident.ident, node_ty))
35+
} else {
36+
None
37+
}
38+
} else {
39+
None
40+
};
41+
42+
let vec_ident: Ident = parse_quote!(Vec);
43+
44+
match node_ident_and_type {
45+
Some((node_ident, node_ty)) => {
46+
let (formatter, node_ty_abbrev) = match node_ty {
47+
Type::Reference(TypeReference { elem, .. }) => match &**elem {
48+
Type::Path(TypePath { path, .. }) => {
49+
let last_segment = path.segments.last().unwrap();
50+
let last_segment_ident = &last_segment.ident;
51+
let last_segment_arguments = if last_segment.arguments.is_empty() {
52+
None
53+
} else {
54+
let args = &last_segment.arguments;
55+
Some(quote!(<#args>))
56+
};
57+
match last_segment_ident {
58+
ident if vec_ident == *ident => {
59+
(quote!(crate::FmtAstVec), quote!(#last_segment_ident #last_segment_arguments))
60+
}
61+
_ => (quote!(crate::FmtAst), quote!(#last_segment_ident #last_segment_arguments))
62+
}
63+
},
64+
_ => unreachable!("Infer::infer_enter/infer_exit has sig: infer_..(&mut self, delete: &'ast N) -> Result<(), TypeError>")
65+
},
66+
_ => unreachable!("Infer::infer_enter/infer_exit has sig: infer_..(&mut self, delete: &'ast N) -> Result<(), TypeError>")
67+
};
68+
69+
let node_ty_abbrev = node_ty_abbrev
70+
.to_token_stream()
71+
.to_string()
72+
.replace(" ", "");
73+
74+
let target = format!("eql-mapper::{}", method.to_string().to_uppercase());
75+
76+
let attr: TracingInstrumentAttr = syn::parse2(quote! {
77+
#[tracing::instrument(
78+
target = #target,
79+
level = "trace",
80+
skip(self, #node_ident),
81+
fields(
82+
ast_ty = #node_ty_abbrev,
83+
ast = %#formatter(#node_ident),
84+
),
85+
ret(Debug)
86+
)]
87+
})
88+
.unwrap();
89+
attrs.push(attr.attr);
90+
}
91+
None => {
92+
return quote!(compile_error!(
93+
"could not determine name of node argumemt in Infer impl"
94+
))
95+
.to_token_stream()
96+
.into();
97+
}
98+
}
99+
}
100+
}
101+
102+
input.to_token_stream().into()
103+
}
104+
105+
struct TracingInstrumentAttr {
106+
attr: Attribute,
107+
}
108+
109+
impl Parse for TracingInstrumentAttr {
110+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
111+
Ok(Self {
112+
attr: Attribute::parse_outer(input)?.first().unwrap().clone(),
113+
})
114+
}
115+
}

packages/eql-mapper/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ authors = [
1111
]
1212

1313
[dependencies]
14+
eql-mapper-macros = { path = "../eql-mapper-macros" }
1415
derive_more = { version = "^1.0", features = ["display", "constructor"] }
1516
impl-trait-for-tuples = "0.2.3"
1617
itertools = "^0.13"

packages/eql-mapper/src/eql_mapper.rs

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ use super::importer::{ImportError, Importer};
22
use crate::{
33
inference::{TypeError, TypeInferencer},
44
unifier::{EqlValue, Unifier},
5-
DepMut, Param, ParamError, ScopeError, ScopeTracker, TableResolver, Type, TypeRegistry,
5+
DepMut, Fmt, Param, ParamError, ScopeError, ScopeTracker, TableResolver, Type, TypeRegistry,
66
TypedStatement, Value,
77
};
88
use sqltk::{Break, NodeKey, Visitable, Visitor};
99
use sqltk_parser::ast::{self as ast, Statement};
1010
use std::{
1111
cell::RefCell, collections::HashMap, marker::PhantomData, ops::ControlFlow, rc::Rc, sync::Arc,
1212
};
13-
use tracing::{span, Level};
13+
use tracing::{event, span, Level};
1414

1515
/// Validates that a SQL statement is well-typed with respect to a database schema that contains zero or more columns with
1616
/// EQL types.
@@ -158,14 +158,50 @@ impl<'ast> EqlMapper<'ast> {
158158
|| -> Result<_, EqlMapperError> { Ok((projection?, params?, literals?, node_types?)) };
159159

160160
match combine_results() {
161-
Ok((projection, params, literals, node_types)) => Ok(TypedStatement {
162-
statement,
163-
projection,
164-
params,
165-
literals,
166-
node_types: Arc::new(node_types),
167-
}),
168-
Err(err) => Err(err),
161+
Ok((projection, params, literals, node_types)) => {
162+
event!(
163+
target: "eql-mapper::EVENT_RESOLVE_OK",
164+
parent: &span_begin,
165+
Level::TRACE,
166+
projection = %&projection,
167+
params = %Fmt(&params),
168+
literals = %Fmt(&literals),
169+
node_types = %Fmt(&node_types)
170+
);
171+
172+
Ok(TypedStatement {
173+
statement,
174+
projection,
175+
params,
176+
literals,
177+
node_types: Arc::new(node_types),
178+
})
179+
}
180+
Err(err) => {
181+
{
182+
let unifier = &*self.unifier.borrow();
183+
unifier.dump_all_nodes(statement);
184+
unifier.dump_substitutions();
185+
}
186+
187+
let projection = self.projection_type(statement);
188+
let params = self.param_types();
189+
let literals = self.literal_types();
190+
let node_types = self.node_types();
191+
192+
event!(
193+
target: "eql-mapper::EVENT_RESOLVE_ERR",
194+
parent: &span_begin,
195+
Level::TRACE,
196+
err = ?err,
197+
projection = ?projection,
198+
params = ?params,
199+
literals = ?literals,
200+
node_types = ?node_types
201+
);
202+
203+
Err(err)
204+
}
169205
}
170206
}
171207

packages/eql-mapper/src/inference/infer_type_impls/delete_statement.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use eql_mapper_macros::trace_infer;
12
use sqltk_parser::ast::Delete;
23

34
use crate::{
@@ -6,6 +7,7 @@ use crate::{
67
TypeInferencer,
78
};
89

10+
#[trace_infer]
911
impl<'ast> InferType<'ast, Delete> for TypeInferencer<'ast> {
1012
fn infer_exit(&mut self, delete: &'ast Delete) -> Result<(), TypeError> {
1113
let Delete { returning, .. } = delete;

packages/eql-mapper/src/inference/infer_type_impls/expr.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use crate::{
22
inference::{unifier::Type, InferType, TypeError},
33
SqlIdent, TypeInferencer,
44
};
5-
5+
use eql_mapper_macros::trace_infer;
66
use sqltk_parser::ast::{Array, BinaryOperator, Expr, Ident};
77

8+
#[trace_infer]
89
impl<'ast> InferType<'ast, Expr> for TypeInferencer<'ast> {
910
fn infer_exit(&mut self, this_expr: &'ast Expr) -> Result<(), TypeError> {
1011
match this_expr {

packages/eql-mapper/src/inference/infer_type_impls/function.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use eql_mapper_macros::trace_infer;
12
use sqltk_parser::ast::{Function, FunctionArg, FunctionArgExpr, FunctionArguments, Ident};
23

34
use crate::{
@@ -6,6 +7,7 @@ use crate::{
67
SqlIdent, TypeInferencer,
78
};
89

10+
#[trace_infer]
911
impl<'ast> InferType<'ast, Function> for TypeInferencer<'ast> {
1012
fn infer_exit(&mut self, function: &'ast Function) -> Result<(), TypeError> {
1113
if !matches!(function.parameters, FunctionArguments::None) {

packages/eql-mapper/src/inference/infer_type_impls/insert_statement.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ use crate::{
99
unifier::{EqlValue, NativeValue, Value},
1010
ColumnKind, TableColumn, TypeInferencer,
1111
};
12-
12+
use eql_mapper_macros::trace_infer;
1313
use sqltk_parser::ast::{Ident, Insert};
1414

15+
#[trace_infer]
1516
impl<'ast> InferType<'ast, Insert> for TypeInferencer<'ast> {
1617
fn infer_enter(&mut self, insert: &'ast Insert) -> Result<(), TypeError> {
1718
let Insert {

packages/eql-mapper/src/inference/infer_type_impls/query_statement.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use eql_mapper_macros::trace_infer;
12
use sqltk_parser::ast::Query;
23

34
use crate::{
45
inference::{InferType, TypeError},
56
TypeInferencer,
67
};
78

9+
#[trace_infer]
810
impl<'ast> InferType<'ast, Query> for TypeInferencer<'ast> {
911
fn infer_exit(&mut self, query: &'ast Query) -> Result<(), TypeError> {
1012
let Query { body, .. } = query;

0 commit comments

Comments
 (0)