~tomleb/harepls

39d30373ae411028f3b1ed2dd955b84686616015 — Tom Lebreux 4 months ago f093545 master
Invalidate identfinder result when local variable of same name

We want more intelligent gotodefinition and gototypedefinition, so we
need to be aware of the local variables that could shadow
functions, globals, etc.

As a first step, we invalidate an identfinder result whenever the ident
is shadowed by a binding in scope stack.
A hare/traversal/+test/scoped_bindings.ha => hare/traversal/+test/scoped_bindings.ha +32 -0
@@ 0,0 1,32 @@
use fmt;
use hare::ast;
use hare::lex;
use strings;

@test fn scope_find_by_name() void = {
	let bindings = []: scoped_bindings;
	defer scope_finish(bindings);

	scope_push(&bindings);
	scope_push_binding(&bindings, strings::dup("d"), ast::ident_dup(["typed"]));
	scope_pop_binding(&bindings);
	scope_pop(&bindings);
	scope_push(&bindings);
	scope_push_binding(&bindings, strings::dup("a"), unresolved_type);
	scope_push_binding(&bindings, strings::dup("c"), ast::ident_dup(["typec"]));
	scope_push(&bindings);
	scope_push_binding(&bindings, strings::dup("a"), ast::ident_dup(["typea"]));
	scope_push_binding(&bindings, strings::dup("b"), ast::ident_dup(["typeb"]));
	scope_push(&bindings);
	scope_push_binding(&bindings, strings::dup("c"), unresolved_type);

	let a = scope_find_by_name(&bindings, "a") as ast::ident;
	assert(ast::ident_eq(a, ["typea"]));

	let b = scope_find_by_name(&bindings, "b") as ast::ident;
	assert(ast::ident_eq(b, ["typeb"]));

	assert(scope_find_by_name(&bindings, "c") is unresolved_type);
	assert(scope_find_by_name(&bindings, "d") is void);
	assert(scope_find_by_name(&bindings, "e") is void);
};

M hare/traversal/identfinder.ha => hare/traversal/identfinder.ha +71 -14
@@ 25,6 25,10 @@ export type identfinder = struct {
	subunit: *ast::subunit,
	current: ast::ident,

	// bindings is used for:
	// - invalidating names that refer to local bindings
	bindings: scoped_bindings,

	result: (func_ident | generic_ident | type_ident | void),
};



@@ 34,11 38,13 @@ export fn newidentfinder(current: ast::ident, loc: lex::location) identfinder = 
		result = void,
		target = loc,
		current = current,
		bindings = [],
		...
	};
};

export fn identfinder_finish(identfinder: *identfinder) void = {
	scope_finish(identfinder.bindings);
	match (identfinder.result) {
	case let ident: func_ident =>
		ast::ident_free(ident: ast::ident);


@@ 53,32 59,81 @@ export fn identfinder_finish(identfinder: *identfinder) void = {
const identfinder_vt: vtable = vtable {
	_type = &identfinder_type,
	expr = &identfinder_expr,
	binding_expr = &identfinder_binding_expr,
	subunit = &identfinder_subunit,
	scope_enter = &identfinder_scope_enter,
	scope_exit = &identfinder_scope_exit,
	...
};

fn identfinder_scope_enter(v: *visitor) step = {
	let v = v: *identfinder;
	scope_push(&v.bindings);
	return step::OK;
};

fn identfinder_scope_exit(v: *visitor) step = {
	let v = v: *identfinder;

	scope_pop(&v.bindings);
	return step::OK;
};

fn identfinder_binding_expr(v: *visitor, expr: *ast::binding_expr) step = {
	let v = v: *identfinder;

	for (let i = 0z; i < len(expr.bindings); i += 1) {
		const binding = expr.bindings[i];
		// TODO: binding_unpack
		let name = match (binding.name) {
		case let s: str => yield s;
		case => continue;
		};

		// XXX: Just invalidate stuff for now..
		// match (binding._type) {
		// case let _type: *ast::_type =>
		// 	scope_push_binding(&v.bindings, strings::dup(name), ast::ident_dup([]));
		// 	continue;
		// case null => void;
		// };

		scope_push_binding(&v.bindings, strings::dup(name), unresolved_type);
	};

	return step::OK;
};

fn identfinder_setfunc(v: *identfinder, ident: func_ident) void = {
	match (v.result) {
	case void =>
		v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): func_ident;
	case => void;
	if (!(v.result is void)) {
		return;
	};

	if (!(scope_find_by_name(&v.bindings, ident[len(ident)-1]) is void)) {
		return;
	};

	v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): func_ident;
};

fn identfinder_setgeneric(v: *identfinder, ident: generic_ident) void = {
	match (v.result) {
	case void =>
		v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): generic_ident;
	case => void;
	if (!(v.result is void)) {
		return;
	};

	if (!(scope_find_by_name(&v.bindings, ident[len(ident)-1]) is void)) {
		return;
	};

	v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): generic_ident;
};

fn identfinder_settype(v: *identfinder, ident: type_ident) void = {
	match (v.result) {
	case void =>
		v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): type_ident;
	case => void;
	if (!(v.result is void)) {
		return;
	};

	v.result = util::canonical_ident(v.current, v.subunit, ident: ast::ident): type_ident;
};

fn identfinder_subunit(v: *visitor, subunit: *ast::subunit) step = {


@@ 94,6 149,8 @@ fn identfinder_expr(v: *visitor, expr: *ast::expr) step = {
 		return step::OK;
 	};

	// Check if it's a func_expr
	// eg: foo()
	match (identfinder_expr_func(v, expr)) {
	case let ident: ast::ident =>
		identfinder_setfunc(v, ident);


@@ 175,8 232,8 @@ fn get_expr_ident_relaxed(expr: *ast::expr) (ast::ident | void) = {
// Only [], * and alias can be "relaxed". Meaning the cursor can be anywhere
// and we should be allowed to find the ident.
//     +-- cursor
//     V 
// eg: []*[]foo, 
//     V
// eg: []*[]foo,
fn get_type_ident_relaxed(_type: *ast::_type) (ast::ident | void) = {
	match (_type.repr) {
	case let alias: ast::alias_type =>

A hare/traversal/scope_bindings.ha => hare/traversal/scope_bindings.ha +66 -0
@@ 0,0 1,66 @@
use fmt;
use hare::ast;
use strings;

export type unresolved_type = void;
export type binding = struct {
	name: str,
	type_ident: (ast::ident | unresolved_type),
};
export type scoped_bindings = [][]binding;

export fn scope_push(b: *scoped_bindings) void = {
	append(b, []: []binding);
};

export fn scope_pop(b: *scoped_bindings) void = {
	const i = len(b) - 1;
	free(b[i]);
	delete(b[i]);
};

export fn scope_push_binding(b: *scoped_bindings, name: str, type_ident: (ast::ident | unresolved_type)) void = {
	let binding = binding {
		name = name,
		type_ident = type_ident,
	};
	append(b[len(b)-1], binding);
};

export fn scope_pop_binding(b: *scoped_bindings) void = {
	const i = len(b) - 1;
	const j = len(b[i]) - 1;
	free(b[i][j].name);
	match (b[i][j].type_ident) {
	case let ident: ast::ident =>
		ast::ident_free(ident);
	case unresolved_type => void;
	};
	delete(b[i][j]);
};

// void means no binding with this name at all
export fn scope_find_by_name(b: *scoped_bindings, name: str) (ast::ident | unresolved_type | void) = {
	for (let i = 0z; i < len(b); i += 1) {
		let rev_i = len(b) - i - 1;
		for (let j = 0z; j < len(b[rev_i]); j += 1) {
			let rev_j = len(b[rev_i]) - j - 1;
			if (b[rev_i][rev_j].name == name) {
				return b[rev_i][rev_j].type_ident;
			};
		};
	};
};

export fn scope_finish(b: scoped_bindings) void = {
	const length = len(b);
	for (let i = 0z; i < length; i += 1) {
		const rev_i = (length - i) - 1;
		const length_i = len(b[rev_i]);
		for (let j = 0z; j < length_i; j += 1) {
			scope_pop_binding(&b);
		};
		scope_pop(&b);
	};
	free(b);
};