mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 20:28:02 +00:00
generate the RocFunction struct
This commit is contained in:
parent
98ba49baf6
commit
6ead631c82
8 changed files with 266 additions and 63 deletions
|
@ -5233,7 +5233,7 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
|
|||
builder.build_return(Some(&size));
|
||||
}
|
||||
|
||||
pub fn build_proc<'a, 'ctx, 'env>(
|
||||
fn build_proc<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_interner: &mut STLayoutInterner<'a>,
|
||||
mod_solutions: &'a ModSolutions,
|
||||
|
|
|
@ -10999,6 +10999,7 @@ pub fn generate_glue_procs<'a, I: Interner<'a, Layout<'a>>>(
|
|||
}
|
||||
},
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
// TODO generate closure caller
|
||||
stack.push(lambda_set.runtime_representation(layout_interner))
|
||||
}
|
||||
Layout::RecursivePointer => {
|
||||
|
|
|
@ -29,6 +29,7 @@ strum_macros = "0.24"
|
|||
indexmap = "1.8.1"
|
||||
fnv = "1.0.7"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.3.0"
|
||||
tempfile = "3.2.0"
|
||||
|
|
|
@ -290,9 +290,7 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
|
|||
// This is recursively pointing to a type that should already have been added,
|
||||
// so no extra work needs to happen.
|
||||
}
|
||||
RocType::Function(RocFn { .. }) => {
|
||||
// TODO actually generate glue functions!
|
||||
}
|
||||
RocType::Function(roc_fn) => add_function(target_info, roc_fn, types, impls),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -638,6 +636,7 @@ fn add_tag_union(
|
|||
types: &Types,
|
||||
impls: &mut Impls,
|
||||
) {
|
||||
dbg!("adding", name);
|
||||
let name = escape_kw(name.to_string());
|
||||
|
||||
// We should never be attempting to generate glue for empty tag unions;
|
||||
|
@ -1809,6 +1808,83 @@ fn add_enumeration<I: ExactSizeIterator<Item = S>, S: AsRef<str> + Display>(
|
|||
add_decl(impls, None, target_info, buf);
|
||||
}
|
||||
|
||||
fn add_function(
|
||||
// name: &str,
|
||||
target_info: TargetInfo,
|
||||
roc_fn: &RocFn,
|
||||
types: &Types,
|
||||
impls: &mut Impls,
|
||||
) {
|
||||
let name = escape_kw(roc_fn.name.to_string());
|
||||
// let derive = derive_str(types.get_type(struct_id), types, true);
|
||||
let derive = "";
|
||||
let pub_str = "pub ";
|
||||
let mut buf;
|
||||
|
||||
let repr = "C";
|
||||
buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
|
||||
|
||||
let fields = [("closure_data", &roc_fn.lambda_set)];
|
||||
|
||||
for (label, type_id) in fields {
|
||||
let type_str = type_name(*type_id, types);
|
||||
|
||||
// Tag union payloads have numbered fields, so we prefix them
|
||||
// with an "f" because Rust doesn't allow struct fields to be numbers.
|
||||
let label = escape_kw(label.to_string());
|
||||
|
||||
writeln!(buf, "{INDENT}pub {label}: {type_str},",).unwrap();
|
||||
}
|
||||
|
||||
buf.push('}');
|
||||
|
||||
buf.push('\n');
|
||||
buf.push('\n');
|
||||
|
||||
let arguments = "";
|
||||
let argument_types = "";
|
||||
let argument_names = "";
|
||||
let extern_name = "roc__mainForHost_1__Fx2_caller";
|
||||
|
||||
let return_type_str = type_name(roc_fn.ret, types);
|
||||
|
||||
writeln!(buf, "impl {name} {{").unwrap();
|
||||
writeln!(
|
||||
buf,
|
||||
"{INDENT}pub fn force_thunk(self, {arguments}) -> {return_type_str} {{"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(buf, "{INDENT}{INDENT}extern \"C\" {{").unwrap();
|
||||
writeln!(
|
||||
buf,
|
||||
"{INDENT}{INDENT}{INDENT} fn {extern_name}(output: *mut {return_type_str}, {argument_types});"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(buf, "{INDENT}{INDENT}}}").unwrap();
|
||||
|
||||
writeln!(buf).unwrap();
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"{INDENT}{INDENT}let mut output = std::mem::MaybeUninit::uninit();"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"{INDENT}{INDENT}unsafe {{ {extern_name}(output.as_mut_ptr(), {argument_names}) }};"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(buf, "{INDENT}{INDENT}unsafe {{ output.assume_init() }}").unwrap();
|
||||
|
||||
writeln!(buf, "{INDENT}}}").unwrap();
|
||||
buf.push('}');
|
||||
|
||||
add_decl(impls, None, target_info, buf);
|
||||
}
|
||||
|
||||
fn add_struct(
|
||||
name: &str,
|
||||
target_info: TargetInfo,
|
||||
|
|
|
@ -17,7 +17,7 @@ use roc_mono::layout::{
|
|||
};
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::{
|
||||
subs::{Content, FlatType, GetSubsSlice, Subs, UnionLabels, UnionTags, Variable},
|
||||
subs::{Content, FlatType, GetSubsSlice, Label, Subs, UnionLabels, Variable},
|
||||
types::{AliasKind, RecordField},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
@ -393,11 +393,13 @@ impl Types {
|
|||
Function(RocFn {
|
||||
name: name_a,
|
||||
args: args_a,
|
||||
lambda_set: lambda_a,
|
||||
ret: ret_a,
|
||||
}),
|
||||
Function(RocFn {
|
||||
name: name_b,
|
||||
args: args_b,
|
||||
lambda_set: lambda_b,
|
||||
ret: ret_b,
|
||||
}),
|
||||
) => {
|
||||
|
@ -405,6 +407,10 @@ impl Types {
|
|||
// with the same type could have completely different implementations!
|
||||
if name_a == name_b
|
||||
&& args_a.len() == args_b.len()
|
||||
&& self.is_equivalent_help(
|
||||
self.get_type_or_pending(*lambda_a),
|
||||
self.get_type_or_pending(*lambda_b),
|
||||
)
|
||||
&& self.is_equivalent_help(
|
||||
self.get_type_or_pending(*ret_a),
|
||||
self.get_type_or_pending(*ret_b),
|
||||
|
@ -625,6 +631,7 @@ impl RocStructFields {
|
|||
pub struct RocFn {
|
||||
pub name: String,
|
||||
pub args: Vec<TypeId>,
|
||||
pub lambda_set: TypeId,
|
||||
pub ret: TypeId,
|
||||
}
|
||||
|
||||
|
@ -965,6 +972,15 @@ fn add_type_help<'a>(
|
|||
arg_type_ids.push(add_type_help(env, arg_layout, *arg_var, None, types));
|
||||
}
|
||||
|
||||
let lambda_set_type_id = {
|
||||
let lambda_set_layout = env
|
||||
.layout_cache
|
||||
.from_var(env.arena, *closure_var, env.subs)
|
||||
.expect("Something weird ended up in the content");
|
||||
|
||||
add_type_help(env, lambda_set_layout, *closure_var, None, types)
|
||||
};
|
||||
|
||||
let ret_type_id = {
|
||||
let ret_layout = env
|
||||
.layout_cache
|
||||
|
@ -975,16 +991,15 @@ fn add_type_help<'a>(
|
|||
};
|
||||
|
||||
let name = format!("RocFunction_{:?}", closure_var);
|
||||
let fn_type_id = types.add_named(
|
||||
&env.layout_cache.interner,
|
||||
name.clone(),
|
||||
|
||||
let fn_type_id = add_function(env, name, types, layout, |name| {
|
||||
RocType::Function(RocFn {
|
||||
name,
|
||||
args: arg_type_ids.clone(),
|
||||
lambda_set: lambda_set_type_id,
|
||||
ret: ret_type_id,
|
||||
}),
|
||||
layout,
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
types.depends(fn_type_id, ret_type_id);
|
||||
|
||||
|
@ -1141,7 +1156,16 @@ fn add_type_help<'a>(
|
|||
|
||||
type_id
|
||||
}
|
||||
Content::LambdaSet(_) => todo!(),
|
||||
Content::LambdaSet(lambda_set) => {
|
||||
let tags = lambda_set.solved;
|
||||
|
||||
if tags.is_empty() {
|
||||
// this function does not capture anything. Represent that at runtime as a unit value
|
||||
types.add_anonymous(&env.layout_cache.interner, RocType::Unit, layout)
|
||||
} else {
|
||||
add_tag_union(env, opt_name, &tags, var, types, layout, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1336,6 +1360,27 @@ fn add_builtin_type<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn add_function<'a, F>(
|
||||
env: &mut Env<'a>,
|
||||
name: String,
|
||||
types: &mut Types,
|
||||
layout: Layout<'a>,
|
||||
to_type: F,
|
||||
) -> TypeId
|
||||
where
|
||||
F: FnOnce(String) -> RocType,
|
||||
{
|
||||
// let subs = env.subs;
|
||||
// let arena = env.arena;
|
||||
|
||||
types.add_named(
|
||||
&env.layout_cache.interner,
|
||||
name.clone(),
|
||||
to_type(name),
|
||||
layout,
|
||||
)
|
||||
}
|
||||
|
||||
fn add_struct<'a, I, L, F>(
|
||||
env: &mut Env<'a>,
|
||||
name: String,
|
||||
|
@ -1421,22 +1466,34 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn add_tag_union<'a>(
|
||||
trait UnionTag: Label + std::fmt::Debug {
|
||||
fn union_tag_name(&self) -> String;
|
||||
}
|
||||
|
||||
impl UnionTag for TagName {
|
||||
fn union_tag_name(&self) -> String {
|
||||
self.0.as_str().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl UnionTag for Symbol {
|
||||
fn union_tag_name(&self) -> String {
|
||||
format!("C{:?}_{}", self.module_id(), self.ident_id().index())
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_union_type_from_layout<'a>(
|
||||
env: &mut Env<'a>,
|
||||
opt_name: Option<Symbol>,
|
||||
union_tags: &UnionTags,
|
||||
name: String,
|
||||
union_tags: &UnionLabels<impl UnionTag>,
|
||||
var: Variable,
|
||||
types: &mut Types,
|
||||
layout: Layout<'a>,
|
||||
rec_root: Option<Variable>,
|
||||
) -> TypeId {
|
||||
) -> RocTagUnion {
|
||||
let subs = env.subs;
|
||||
let name = match opt_name {
|
||||
Some(sym) => sym.as_str(env.interns).to_string(),
|
||||
None => env.enum_names.get_name(var),
|
||||
};
|
||||
|
||||
let tag_union_type = match layout {
|
||||
match layout {
|
||||
_ if union_tags.is_newtype_wrapper(subs)
|
||||
&& matches!(
|
||||
subs.get_content_without_compacting(var),
|
||||
|
@ -1453,7 +1510,7 @@ fn add_tag_union<'a>(
|
|||
|
||||
RocTagUnion::SingleTagStruct {
|
||||
name: name.clone(),
|
||||
tag_name: tag_name.to_string(),
|
||||
tag_name,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
@ -1509,7 +1566,7 @@ fn add_tag_union<'a>(
|
|||
// e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
|
||||
RocTagUnion::NonNullableUnwrapped {
|
||||
name: name.clone(),
|
||||
tag_name: tag_name.to_string(),
|
||||
tag_name,
|
||||
payload: opt_payload.unwrap(),
|
||||
}
|
||||
}
|
||||
|
@ -1620,20 +1677,51 @@ fn add_tag_union<'a>(
|
|||
|
||||
RocTagUnion::SingleTagStruct {
|
||||
name: name.clone(),
|
||||
tag_name: tag_name.to_string(),
|
||||
tag_name,
|
||||
payload: payload_fields,
|
||||
}
|
||||
}
|
||||
Layout::LambdaSet(_) => {
|
||||
todo!();
|
||||
}
|
||||
Layout::LambdaSet(lambda_set) => tag_union_type_from_layout(
|
||||
env,
|
||||
opt_name,
|
||||
name,
|
||||
union_tags,
|
||||
var,
|
||||
types,
|
||||
lambda_set.runtime_representation(&env.layout_cache.interner),
|
||||
),
|
||||
Layout::RecursivePointer => {
|
||||
// A single-tag union which only wraps itself is erroneous and should have
|
||||
// been turned into an error earlier in the process.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tag_union<'a>(
|
||||
env: &mut Env<'a>,
|
||||
opt_name: Option<Symbol>,
|
||||
union_tags: &UnionLabels<impl UnionTag>,
|
||||
var: Variable,
|
||||
types: &mut Types,
|
||||
layout: Layout<'a>,
|
||||
rec_root: Option<Variable>,
|
||||
) -> TypeId {
|
||||
let name = match opt_name {
|
||||
Some(sym) => sym.as_str(env.interns).to_string(),
|
||||
None => env.enum_names.get_name(var),
|
||||
};
|
||||
|
||||
let tag_union_type = tag_union_type_from_layout(
|
||||
env,
|
||||
opt_name,
|
||||
name.to_string(),
|
||||
union_tags,
|
||||
var,
|
||||
types,
|
||||
layout,
|
||||
);
|
||||
|
||||
let typ = RocType::TagUnion(tag_union_type);
|
||||
let type_id = types.add_named(&env.layout_cache.interner, name, typ, layout);
|
||||
|
||||
|
@ -1645,14 +1733,14 @@ fn add_tag_union<'a>(
|
|||
}
|
||||
|
||||
fn add_int_enumeration(
|
||||
union_tags: &UnionLabels<TagName>,
|
||||
union_tags: &UnionLabels<impl UnionTag>,
|
||||
subs: &Subs,
|
||||
name: &str,
|
||||
int_width: IntWidth,
|
||||
) -> RocTagUnion {
|
||||
let tags: Vec<String> = union_tags
|
||||
.iter_from_subs(subs)
|
||||
.map(|(tag_name, _)| tag_name.0.as_str().to_string())
|
||||
.map(|(tag_name, _)| tag_name.union_tag_name())
|
||||
.collect();
|
||||
RocTagUnion::Enumeration {
|
||||
name: name.to_string(),
|
||||
|
@ -1663,7 +1751,7 @@ fn add_int_enumeration(
|
|||
|
||||
fn union_tags_to_types<'a>(
|
||||
name: &str,
|
||||
union_tags: &UnionLabels<TagName>,
|
||||
union_tags: &UnionLabels<impl UnionTag>,
|
||||
subs: &Subs,
|
||||
env: &mut Env<'a>,
|
||||
types: &mut Types,
|
||||
|
@ -1673,7 +1761,7 @@ fn union_tags_to_types<'a>(
|
|||
let mut tags: Vec<(String, Vec<Variable>)> = union_tags
|
||||
.iter_from_subs(subs)
|
||||
.map(|(tag_name, payload_vars)| {
|
||||
let name_str = tag_name.0.as_str().to_string();
|
||||
let name_str = tag_name.union_tag_name();
|
||||
|
||||
(name_str, payload_vars.to_vec())
|
||||
})
|
||||
|
@ -1698,25 +1786,25 @@ fn union_tags_to_types<'a>(
|
|||
}
|
||||
|
||||
fn single_tag_payload<'a>(
|
||||
union_tags: &'a UnionLabels<TagName>,
|
||||
union_tags: &'a UnionLabels<impl UnionTag>,
|
||||
subs: &'a Subs,
|
||||
) -> (&'a str, &'a [Variable]) {
|
||||
) -> (String, &'a [Variable]) {
|
||||
let mut iter = union_tags.iter_from_subs(subs);
|
||||
let (tag_name, payload_vars) = iter.next().unwrap();
|
||||
// This should be a single-tag union.
|
||||
debug_assert_eq!(iter.next(), None);
|
||||
debug_assert!(iter.next().is_none());
|
||||
|
||||
(tag_name.0.as_str(), payload_vars)
|
||||
(tag_name.union_tag_name(), payload_vars)
|
||||
}
|
||||
|
||||
fn single_tag_payload_fields<'a, 'b>(
|
||||
union_tags: &'b UnionLabels<TagName>,
|
||||
union_tags: &'b UnionLabels<impl UnionTag>,
|
||||
subs: &'b Subs,
|
||||
layout: Layout<'a>,
|
||||
field_layouts: &[Layout<'a>],
|
||||
env: &mut Env<'a>,
|
||||
types: &mut Types,
|
||||
) -> (&'b str, RocSingleTagPayload) {
|
||||
) -> (String, RocSingleTagPayload) {
|
||||
// There should be a glue_procs_by_layout entry iff this layout has a closure in it,
|
||||
// so we shouldn't need to separately check that. Howeevr, we still do a debug_assert
|
||||
// anyway just so we have some warning in case that relationship somehow didn't hold!
|
||||
|
|
|
@ -5,11 +5,13 @@ platform "echo-in-rust"
|
|||
imports []
|
||||
provides [mainForHost]
|
||||
|
||||
Op : [StdoutWrite Str ({} -> Op), StderrWrite Str ({} -> Op), Done]
|
||||
|
||||
|
||||
# mainForHost : { bar: Str, foo: I64 -> I64 }
|
||||
# mainForHost = { bar: main, foo: \x -> x }
|
||||
|
||||
mainForHost : Op
|
||||
mainForHost : [StdoutWrite Str (({} -> Op) as Fx1), StderrWrite Str (({} -> Op) as Fx2), Done] as Op
|
||||
mainForHost = main
|
||||
|
||||
# mainForHost : { x: Str, y: {} -> Str }
|
||||
# mainForHost =
|
||||
# y = "foo"
|
||||
#
|
||||
# when main is
|
||||
# _ -> { x: "bar", y: \{} -> y }
|
||||
|
|
|
@ -86,6 +86,56 @@ union union_Op {
|
|||
))]
|
||||
//TODO HAS CLOSURE 2
|
||||
|
||||
#[cfg(any(
|
||||
target_arch = "arm",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "wasm32",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
))]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RocFunction_65 {
|
||||
pub closure_data: (),
|
||||
}
|
||||
|
||||
impl RocFunction_65 {
|
||||
pub fn force_thunk(self, ) -> Op {
|
||||
extern "C" {
|
||||
fn roc__mainForHost_1__Fx2_caller(output: *mut Op, );
|
||||
}
|
||||
|
||||
let mut output = std::mem::MaybeUninit::uninit();
|
||||
unsafe { roc__mainForHost_1__Fx2_caller(output.as_mut_ptr(), ) };
|
||||
unsafe { output.assume_init() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_arch = "arm",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "wasm32",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
))]
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RocFunction_67 {
|
||||
pub closure_data: (),
|
||||
}
|
||||
|
||||
impl RocFunction_67 {
|
||||
pub fn force_thunk(self, ) -> Op {
|
||||
extern "C" {
|
||||
fn roc__mainForHost_1__Fx2_caller(output: *mut Op, );
|
||||
}
|
||||
|
||||
let mut output = std::mem::MaybeUninit::uninit();
|
||||
unsafe { roc__mainForHost_1__Fx2_caller(output.as_mut_ptr(), ) };
|
||||
unsafe { output.assume_init() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86_64"
|
||||
|
@ -97,21 +147,6 @@ union union_Op {
|
|||
_sizer: [u8; 8],
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RocFunction_3 {
|
||||
closure_data: T
|
||||
}
|
||||
|
||||
impl RocFunction_3 {
|
||||
pub fn call(self) -> U {
|
||||
extern "C" {
|
||||
fn call_the_closure(T) -> U ;
|
||||
}
|
||||
|
||||
call_the_closure(self.closure_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Op {
|
||||
#[cfg(any(
|
||||
target_arch = "arm",
|
||||
|
@ -224,12 +259,12 @@ impl Op {
|
|||
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 1.
|
||||
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
|
||||
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
|
||||
pub unsafe fn get_StderrWrite_1(&self) -> TODO_roc_function_69 {
|
||||
pub unsafe fn get_StderrWrite_1(&self) -> RocFunction_67 {
|
||||
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "roc__getter__3"]
|
||||
fn getter(_: *mut TODO_roc_function_69, _: *const Op);
|
||||
fn getter(_: *mut RocFunction_67, _: *const Op);
|
||||
}
|
||||
|
||||
let mut ret = core::mem::MaybeUninit::uninit();
|
||||
|
@ -346,12 +381,12 @@ impl Op {
|
|||
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 1.
|
||||
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
|
||||
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
|
||||
pub unsafe fn get_StdoutWrite_1(&self) -> TODO_roc_function_70 {
|
||||
pub unsafe fn get_StdoutWrite_1(&self) -> RocFunction_65 {
|
||||
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "roc__getter__3"]
|
||||
fn getter(_: *mut TODO_roc_function_70, _: *const Op);
|
||||
fn getter(_: *mut RocFunction_65, _: *const Op);
|
||||
}
|
||||
|
||||
let mut ret = core::mem::MaybeUninit::uninit();
|
||||
|
|
|
@ -105,7 +105,7 @@ pub extern "C" fn rust_main() -> i32 {
|
|||
match dbg!(op.discriminant()) {
|
||||
StdoutWrite => {
|
||||
let output: RocStr = unsafe { op.get_StdoutWrite_0() };
|
||||
op = unsafe { op.get_StdoutWrite_1() };
|
||||
op = unsafe { op.get_StdoutWrite_1().force_thunk() };
|
||||
|
||||
dbg!(&output);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue