From 39bb06d5d86d133655237280c56a2b07ef86e6b9 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 2 May 2016 16:54:29 -0500 Subject: [PATCH 1/5] Move ambiguity warnings from definition to usage: register ambiguities For now, this disables any kind of ambiguity warning/error --- src/alloc.c | 2 ++ src/gf.c | 20 ++++++++++---------- src/jltypes.c | 6 ++++-- src/julia.h | 2 ++ src/typemap.c | 1 - test/ambiguous.jl | 24 ++++++++++++++++++++++++ test/choosetests.jl | 2 +- test/misc.jl | 5 ----- 8 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 test/ambiguous.jl diff --git a/src/alloc.c b/src/alloc.c index 72e7d7956be31..7db4a6ec24d2f 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -542,6 +542,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(void) m->tfunc.unknown = jl_nothing; m->sig = NULL; m->tvars = NULL; + m->ambig = NULL; m->roots = NULL; m->module = jl_current_module; m->lambda_template = NULL; @@ -567,6 +568,7 @@ jl_method_t *jl_new_method(jl_lambda_info_t *definition, jl_sym_t *name, jl_tupl if (jl_svec_len(tvars) == 1) tvars = (jl_svec_t*)jl_svecref(tvars, 0); m->tvars = tvars; + m->ambig = jl_nothing; JL_GC_PUSH1(&m); // the front end may add this lambda to multiple methods; make a copy if so jl_method_t *oldm = definition->def; diff --git a/src/gf.c b/src/gf.c index 91429f3d20546..b24ec616b69fc 100644 --- a/src/gf.c +++ b/src/gf.c @@ -765,16 +765,16 @@ static int check_ambiguous_visitor(jl_typemap_entry_t *oldentry, struct typemap_ // ok, intersection is covered return 1; } - JL_STREAM *s = JL_STDERR; - jl_printf(s, "WARNING: New definition \n "); - jl_static_show_func_sig(s, (jl_value_t*)type); - print_func_loc(s, m); - jl_printf(s, "\nis ambiguous with: \n "); - jl_static_show_func_sig(s, (jl_value_t*)sig); - print_func_loc(s, oldentry->func.method); - jl_printf(s, ".\nTo fix, define \n "); - jl_static_show_func_sig(s, isect); - jl_printf(s, "\nbefore the new definition.\n"); + jl_method_t *mambig = oldentry->func.method; + if (m->ambig == jl_nothing) { + m->ambig = (jl_value_t*) jl_alloc_cell_1d(0); + } + if (mambig->ambig == jl_nothing) { + mambig->ambig = (jl_value_t*) jl_alloc_cell_1d(0); + } + jl_cell_1d_push((jl_array_t*) m->ambig, (jl_value_t*) mambig); + jl_cell_1d_push((jl_array_t*) mambig->ambig, (jl_value_t*) m); + return 1; // there may be multiple ambiguities, keep going } return 1; } diff --git a/src/jltypes.c b/src/jltypes.c index 8da2246931222..ef7e695be7020 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3508,13 +3508,14 @@ void jl_init_types(void) jl_method_type = jl_new_datatype(jl_symbol("Method"), jl_any_type, jl_emptysvec, - jl_svec(15, + jl_svec(16, jl_symbol("name"), jl_symbol("module"), jl_symbol("file"), jl_symbol("line"), jl_symbol("sig"), jl_symbol("tvars"), + jl_symbol("ambig"), jl_symbol("specializations"), jl_symbol("tfunc"), jl_symbol("lambda_template"), @@ -3524,13 +3525,14 @@ void jl_init_types(void) jl_symbol("isstaged"), jl_symbol("needs_sparam_vals_ducttape"), jl_symbol("")), - jl_svec(15, + jl_svec(16, jl_sym_type, jl_module_type, jl_sym_type, jl_int32_type, jl_type_type, jl_any_type, + jl_any_type, // Union{Array, Void} jl_array_any_type, jl_any_type, jl_any_type, diff --git a/src/julia.h b/src/julia.h index 8066af83bc133..7faa2de67ee7e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -198,6 +198,8 @@ typedef struct _jl_method_t { jl_tupletype_t *sig; // bound type variables (static parameters). redundant with TypeMapEntry->tvars jl_svec_t *tvars; + // list of potentially-ambiguous methods (nothing = none, Vector{Any} of Methods otherwise) + jl_value_t *ambig; // array of all lambda infos with code generated from this one jl_array_t *specializations; diff --git a/src/typemap.c b/src/typemap.c index c23e69f2cf5ab..8fe5a3e64f9dc 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -998,4 +998,3 @@ static void jl_typemap_list_insert_sorted(jl_typemap_entry_t **pml, jl_value_t * #ifdef __cplusplus } #endif - diff --git a/test/ambiguous.jl b/test/ambiguous.jl new file mode 100644 index 0000000000000..0fc33d6c0788a --- /dev/null +++ b/test/ambiguous.jl @@ -0,0 +1,24 @@ +# DO NOT CHANGE LINE NUMBERS BELOW +@noinline foo(x, y) = 1 +@noinline foo(x::Integer, y) = 2 +@noinline foo(x, y::Integer) = 3 +@noinline foo(x::Int, y::Int) = 4 +@noinline foo(x::Number, y) = 5 +# END OF LINE NUMBER SENSITIVITY + +ambigs = Any[[], [3], [2,5], [], [3]] + +mt = methods(foo) + +getline(m::Method) = m.line - 1 # -1 for the comment at the top + +for m in mt + ln = getline(m) + atarget = ambigs[ln] + if isempty(atarget) + @test m.ambig == nothing + else + aln = Int[getline(a) for a in m.ambig] + @test sort(aln) == atarget + end +end diff --git a/test/choosetests.jl b/test/choosetests.jl index 2c6b4eb99a225..4c7ec97a83b56 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -33,7 +33,7 @@ function choosetests(choices = []) "markdown", "base64", "serialize", "misc", "threads", "enums", "cmdlineargs", "i18n", "workspace", "libdl", "int", "checked", "intset", "floatfuncs", "compile", "parallel", "inline", - "boundscheck", "error" + "boundscheck", "error", "ambiguous" ] if Base.USE_GPL_LIBS diff --git a/test/misc.jl b/test/misc.jl index 606a5e58b66dc..65cde1fbb5803 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -130,11 +130,6 @@ func4union(::Union{Type4Union,Int}) = () let redir_err = "redirect_stderr(STDOUT)" exename = Base.julia_cmd() - script = "$redir_err; f(a::Number, b...) = 1;f(a, b::Number) = 1" - warning_str = readstring(`$exename -f -e $script`) - @test contains(warning_str, "f(Any, Number)") - @test contains(warning_str, "f(Number, Any...)") - @test contains(warning_str, "f(Number, Number)") script = "$redir_err; module A; f() = 1; end; A.f() = 1" warning_str = readstring(`$exename -f -e $script`) From 827fb970dae4baeb65303650fb8e00054d22131f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 2 May 2016 17:58:52 -0500 Subject: [PATCH 2/5] Throw error for an ambiguous call --- src/builtins.c | 3 +- src/codegen.cpp | 4 +- src/dump.c | 3 ++ src/gf.c | 116 ++++++++++++++++++++++++++++--------------- src/julia_internal.h | 8 +-- src/typemap.c | 22 ++++---- test/ambiguous.jl | 6 +++ test/misc.jl | 1 - 8 files changed, 106 insertions(+), 57 deletions(-) diff --git a/src/builtins.c b/src/builtins.c index 19937b2c7a812..542cd80e2a365 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -948,7 +948,8 @@ static void jl_check_type_tuple(jl_value_t *t, jl_sym_t *name, const char *ctx) JL_CALLABLE(jl_f_applicable) { JL_NARGSV(applicable, 1); - return jl_method_lookup(jl_gf_mtable(args[0]), args, nargs, 1) != NULL ? + jl_typemap_entry_t *entry; + return jl_method_lookup(jl_gf_mtable(args[0]), args, nargs, 1, &entry) != NULL ? jl_true : jl_false; } diff --git a/src/codegen.cpp b/src/codegen.cpp index a036922c9a68f..fd2c3d18cfc34 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1096,12 +1096,14 @@ void *jl_get_llvmf(jl_tupletype_t *tt, bool getwrapper, bool getdeclarations) if (tt != NULL) { linfo = jl_get_specialization1(tt); if (linfo == NULL) { + jl_typemap_entry_t *entry; linfo = jl_method_lookup_by_type( - ((jl_datatype_t*)jl_tparam0(tt))->name->mt, tt, 0, 0); + ((jl_datatype_t*)jl_tparam0(tt))->name->mt, tt, 0, 0, &entry); if (linfo == NULL) { JL_GC_POP(); return NULL; } + check_ambig_call(linfo->def, tt); } } if (linfo == NULL) { diff --git a/src/dump.c b/src/dump.c index 7bb29b6624b52..bb7be8cbd474a 100644 --- a/src/dump.c +++ b/src/dump.c @@ -841,6 +841,7 @@ static void jl_serialize_value_(ios_t *s, jl_value_t *v) write_int32(s, m->line); jl_serialize_value(s, (jl_value_t*)m->sig); jl_serialize_value(s, (jl_value_t*)m->tvars); + jl_serialize_value(s, (jl_value_t*)m->ambig); write_int8(s, m->called); jl_serialize_value(s, (jl_value_t*)m->module); jl_serialize_value(s, (jl_value_t*)m->roots); @@ -1444,6 +1445,8 @@ static jl_value_t *jl_deserialize_value_(ios_t *s, jl_value_t *vtag, jl_value_t jl_gc_wb(m, m->sig); m->tvars = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->tvars); jl_gc_wb(m, m->tvars); + m->ambig = jl_deserialize_value(s, (jl_value_t**)&m->ambig); + jl_gc_wb(m, m->tvars); m->called = read_int8(s); m->module = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->module); jl_gc_wb(m, m->module); diff --git a/src/gf.c b/src/gf.c index b24ec616b69fc..55c81fc58fa9c 100644 --- a/src/gf.c +++ b/src/gf.c @@ -147,6 +147,7 @@ jl_value_t *jl_mk_builtin_func(const char *name, jl_fptr_t fptr) li->def = jl_new_method_uninit(); li->def->name = sname; li->def->lambda_template = li; + li->def->ambig = jl_nothing; jl_methtable_t *mt = jl_gf_mtable(f); jl_typemap_insert(&mt->cache, (jl_value_t*)mt, jl_anytuple_type, jl_emptysvec, NULL, jl_emptysvec, (jl_value_t*)li, 0, &lambda_cache, NULL); return f; @@ -681,26 +682,29 @@ static jl_lambda_info_t *cache_method(jl_methtable_t *mt, union jl_typemap_t *ca return newmeth; } -static jl_lambda_info_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, int cache, int inexact) +static jl_lambda_info_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, int cache, int inexact, jl_typemap_entry_t **entry) { - jl_typemap_entry_t *m = NULL; + *entry = NULL; jl_svec_t *env = jl_emptysvec; jl_method_t *func = NULL; jl_tupletype_t *sig = NULL; - JL_GC_PUSH4(&env, &m, &func, &sig); + JL_GC_PUSH4(&env, entry, &func, &sig); - m = jl_typemap_assoc_by_type(mt->defs, tt, &env, inexact, 1, 0); - if (m == NULL) { + *entry = jl_typemap_assoc_by_type(mt->defs, tt, &env, inexact, 1, 0); + if (*entry == NULL) { JL_GC_POP(); return NULL; } - sig = join_tsig(tt, m->sig); + jl_method_t *m = (*entry)->func.method; + sig = join_tsig(tt, (*entry)->sig); jl_lambda_info_t *nf; if (!cache) - nf = jl_get_specialized(m->func.method, sig, env); - else - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, sig, tt, m, env); + nf = jl_get_specialized(m, sig, env); + else { + check_ambig_call(m, tt); // if ambiguous, don't insert into cache + nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, sig, tt, *entry, env); + } JL_GC_POP(); return nf; } @@ -959,34 +963,41 @@ jl_tupletype_t *arg_type_tuple(jl_value_t **args, size_t nargs) } jl_lambda_info_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *types, - int cache, int inexact) + int cache, int inexact, + jl_typemap_entry_t **entry) { - jl_typemap_entry_t *m = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt)); + *entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt)); jl_lambda_info_t *sf; - if (m) { - sf = m->func.linfo; + if (*entry) { + sf = (*entry)->func.linfo; } else { if (jl_is_leaf_type((jl_value_t*)types)) cache=1; - sf = jl_mt_assoc_by_type(mt, types, cache, inexact); + sf = jl_mt_assoc_by_type(mt, types, cache, inexact, entry); } return sf; } JL_DLLEXPORT int jl_method_exists(jl_methtable_t *mt, jl_tupletype_t *types) { - return jl_method_lookup_by_type(mt, types, 0, 0) != NULL; + jl_typemap_entry_t *entry; + return jl_method_lookup_by_type(mt, types, 0, 0, &entry) != NULL; } -jl_lambda_info_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache) +jl_lambda_info_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache, jl_typemap_entry_t **entry) { - jl_lambda_info_t *sf = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); - if (sf == NULL) { + jl_lambda_info_t *sf; + *entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + if (*entry == NULL) { jl_tupletype_t *tt = arg_type_tuple(args, nargs); JL_GC_PUSH1(&tt); - sf = jl_mt_assoc_by_type(mt, tt, cache, 0); + sf = jl_mt_assoc_by_type(mt, tt, cache, 0, entry); JL_GC_POP(); } + else { + sf = (*entry)->func.linfo; + } + return sf; } @@ -1020,13 +1031,17 @@ jl_lambda_info_t *jl_get_specialization1(jl_tupletype_t *types) // most of the time sf is rooted in mt, but if the method is staged it may // not be the case JL_GC_PUSH1(&sf); + jl_typemap_entry_t *entry; JL_TRY { - sf = jl_method_lookup_by_type(mt, types, 1, 1); + sf = jl_method_lookup_by_type(mt, types, 1, 1, &entry); } JL_CATCH { goto not_found; } if (sf == NULL || sf->code == NULL || sf->inInference) goto not_found; + jl_method_t *m = sf->def; + if (m->ambig != jl_nothing) + check_ambig_call(m, types); if (sf->functionObjectsDecls.functionObject == NULL) { if (sf->fptr != NULL) goto not_found; @@ -1044,6 +1059,19 @@ JL_DLLEXPORT void jl_compile_hint(jl_tupletype_t *types) (void)jl_get_specialization1(types); } +void check_ambig_call(jl_method_t *m, jl_tupletype_t *types) +{ + if (m->ambig == jl_nothing) + return; + for (size_t i = 0; i < jl_array_len(m->ambig); i++) { + jl_method_t *mambig = (jl_method_t*)jl_cellref(m->ambig, i); + if (jl_type_intersection((jl_value_t*)mambig->sig, + (jl_value_t*)types) != (jl_value_t*)jl_bottom_type) { + jl_error("ambiguous"); + } + } +} + // add type of `f` to front of argument tuple type jl_tupletype_t *jl_argtype_with_function(jl_function_t *f, jl_tupletype_t *types) { @@ -1483,16 +1511,16 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) if no generic match, use the concrete one even if inexact otherwise instantiate the generic method and use it */ - jl_lambda_info_t *mfunc = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); - + jl_typemap_entry_t *entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + jl_lambda_info_t *mfunc = NULL; jl_tupletype_t *tt = NULL; - if (mfunc == NULL) { + if (entry == NULL) { // cache miss case tt = arg_type_tuple(args, nargs); // if running inference overwrites this particular method, it becomes // unreachable from the method table, so root mfunc. JL_GC_PUSH2(&tt, &mfunc); - mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0); + mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0, &entry); if (mfunc == NULL) { #ifdef JL_TRACE @@ -1500,10 +1528,16 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) show_call(F, args, nargs); #endif JL_GC_POP(); + jl_method_t *m = entry->func.method; + if (m->ambig != jl_nothing) + check_ambig_call(m, tt); jl_no_method_error((jl_function_t*)F, args, nargs); // unreachable } } + else { + mfunc = entry->func.linfo; + } #ifdef JL_TRACE if (traceen) jl_printf(JL_STDOUT, " at %s:%d\n", jl_symbol_name(mfunc->file), mfunc->line); @@ -1524,11 +1558,11 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_datatype_t *types) { jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; - jl_typemap_entry_t *m = jl_typemap_assoc_by_type(mt->defs, types, /*don't record env*/NULL, + jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->defs, types, /*don't record env*/NULL, /*exact match*/0, /*subtype*/1, /*offs*/0); - if (!m) + if (!entry) return jl_nothing; - return (jl_value_t*)m; + return (jl_value_t*)entry; } // invoke() @@ -1551,9 +1585,9 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs jl_value_t *gf = args[0]; types = (jl_datatype_t*)jl_argtype_with_function(gf, (jl_tupletype_t*)types0); jl_methtable_t *mt = jl_gf_mtable(gf); - jl_typemap_entry_t *m = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types); + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types); - if ((jl_value_t*)m == jl_nothing) { + if ((jl_value_t*)entry == jl_nothing) { jl_no_method_error_bare(gf, (jl_value_t*)types0); // unreachable } @@ -1561,26 +1595,28 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs // now we have found the matching definition. // next look for or create a specialization of this definition. - jl_lambda_info_t *mfunc; - if (m->func.method->invokes.unknown == NULL) - mfunc = NULL; - else - mfunc = jl_typemap_assoc_exact(m->func.method->invokes, args, nargs, jl_cachearg_offset(mt)); - if (mfunc == NULL) { + jl_lambda_info_t *mfunc = NULL; + jl_typemap_entry_t *tm = NULL; + if (entry->func.method->invokes.unknown != NULL) + tm = jl_typemap_assoc_exact(entry->func.method->invokes, args, nargs, jl_cachearg_offset(mt)); + if (tm == NULL) { tt = arg_type_tuple(args, nargs); - if (m->tvars != jl_emptysvec) { + if (entry->tvars != jl_emptysvec) { jl_value_t *ti = - jl_lookup_match((jl_value_t*)tt, (jl_value_t*)m->sig, &tpenv, m->tvars); + jl_lookup_match((jl_value_t*)tt, (jl_value_t*)entry->sig, &tpenv, entry->tvars); assert(ti != (jl_value_t*)jl_bottom_type); (void)ti; } - sig = join_tsig(tt, m->sig); - jl_method_t *func = m->func.method; + sig = join_tsig(tt, entry->sig); + jl_method_t *func = entry->func.method; if (func->invokes.unknown == NULL) func->invokes.unknown = jl_nothing; - mfunc = cache_method(mt, &m->func.method->invokes, m->func.value, sig, tt, m, tpenv); + mfunc = cache_method(mt, &func->invokes, entry->func.value, sig, tt, entry, tpenv); + } + else { + mfunc = tm->func.linfo; } JL_GC_POP(); if (mfunc->inInference || mfunc->inCompile) { diff --git a/src/julia_internal.h b/src/julia_internal.h index b64f30627189b..0856f19e4e81c 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -212,9 +212,11 @@ void jl_lambda_info_set_ast(jl_lambda_info_t *li, jl_value_t *ast); jl_lambda_info_t *jl_get_unspecialized(jl_lambda_info_t *method); jl_lambda_info_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *types, - int cache, int inexact); -jl_lambda_info_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache); + int cache, int inexact, + jl_typemap_entry_t **entry); +jl_lambda_info_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache, jl_typemap_entry_t **entry); jl_value_t *jl_gf_invoke(jl_tupletype_t *types, jl_value_t **args, size_t nargs); +void check_ambig_call(jl_method_t *m, jl_tupletype_t *types); jl_array_t *jl_lam_args(jl_expr_t *l); jl_array_t *jl_lam_vinfo(jl_expr_t *l); @@ -560,7 +562,7 @@ jl_typemap_entry_t *jl_typemap_insert(union jl_typemap_t *cache, jl_value_t *par jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_tupletype_t *types, jl_svec_t **penv, int8_t subtype_inexact__sigseq_useenv, int8_t subtype, int8_t offs); -jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs); +jl_typemap_entry_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs); typedef int (*jl_typemap_visitor_fptr)(jl_typemap_entry_t *l, void *closure); int jl_typemap_visitor(union jl_typemap_t a, jl_typemap_visitor_fptr fptr, void *closure); diff --git a/src/typemap.c b/src/typemap.c index 8fe5a3e64f9dc..09cbc0258a895 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -623,7 +623,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_ jl_typemap_lookup_by_type_(ml, types, subtype_inexact__sigseq_useenv); } -jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs) +jl_typemap_entry_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs) { // NOTE: This function is a huge performance hot spot!! jl_typemap_entry_t *ml; @@ -635,9 +635,9 @@ jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_valu assert(jl_is_datatype(ty)); if (ty == (jl_value_t*)jl_datatype_type && cache->targ != (void*)jl_nothing) { ml_or_cache = mtcache_hash_lookup(cache->targ, a1, 1, offs); - jl_lambda_info_t *li = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); - if (li) - return li; + ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); + if (ml) + return ml; } if (cache->arg1 != (void*)jl_nothing) { ml_or_cache = mtcache_hash_lookup(cache->arg1, ty, 0, offs); @@ -647,7 +647,7 @@ jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_valu jl_value_t *t0 = (jl_value_t*)jl_typeof(a0); if (ml_or_cache.leaf->next==(void*)jl_nothing && n==2 && jl_datatype_nfields(ml_or_cache.leaf->sig)==2 && jl_tparam(ml_or_cache.leaf->sig, 1 - offs) == t0) - return ml_or_cache.leaf->func.linfo; + return ml_or_cache.leaf; if (n==3) { // some manually-unrolled common special cases jl_value_t *a2 = args[2]; @@ -656,18 +656,18 @@ jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_valu if (jl_datatype_nfields(mn->sig)==3 && jl_tparam(mn->sig,1-offs)==t0 && jl_tparam(mn->sig,2)==(jl_value_t*)jl_typeof(a2)) - return mn->func.linfo; + return mn; mn = mn->next; if (mn!=(void*)jl_nothing && jl_datatype_nfields(mn->sig)==3 && jl_tparam(mn->sig,1-offs)==t0 && jl_tparam(mn->sig,2)==(jl_value_t*)jl_typeof(a2)) - return mn->func.linfo; + return mn; } } } - jl_lambda_info_t *li = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); - if (li) - return li; + ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); + if (ml) + return ml; } } ml = cache->linear; @@ -701,7 +701,7 @@ jl_lambda_info_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_valu } } if (i == l) - return ml->func.linfo; + return ml; } } ml = ml->next; diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 0fc33d6c0788a..ed2ebbe141d3c 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -22,3 +22,9 @@ for m in mt @test sort(aln) == atarget end end + +@test foo("hi", "there") == 1 +@test foo(3.1, 3.2) == 5 +@test foo(3, 4) == 4 +@test_throws ErrorException foo(0x03, 4) +@test_throws ErrorException foo(0x03, 4) # test that not inserted into cache diff --git a/test/misc.jl b/test/misc.jl index 65cde1fbb5803..7b6cc4d8e0697 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -130,7 +130,6 @@ func4union(::Union{Type4Union,Int}) = () let redir_err = "redirect_stderr(STDOUT)" exename = Base.julia_cmd() - script = "$redir_err; module A; f() = 1; end; A.f() = 1" warning_str = readstring(`$exename -f -e $script`) @test contains(warning_str, "f()") From 122b5b418d8a62ed917693cbabc910382ce09d25 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 2 May 2016 18:49:49 -0500 Subject: [PATCH 3/5] Refactor: methods returns multiples in case of ambiguity Also: - eliminates a lot of the dedicated error-handling for ambiguous methods in favor of returning NULL - causes `precompile` to return true/false upon success/failure (useful for testing) --- base/docs/helpdb/Base.jl | 2 +- base/essentials.jl | 4 +- base/reflection.jl | 2 +- base/replutil.jl | 20 ++++++- base/serialize.jl | 3 ++ src/alloc.c | 1 + src/cgutils.cpp | 2 +- src/codegen.cpp | 3 +- src/dump.c | 2 +- src/gf.c | 109 +++++++++++++++++++++++++-------------- src/julia.h | 1 + src/julia_internal.h | 8 +-- test/ambiguous.jl | 54 +++++++++++++++---- 13 files changed, 149 insertions(+), 62 deletions(-) diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index 2d9a23f9f3121..828d9908f056d 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -7305,7 +7305,7 @@ digits! """ MethodError(f, args) -A method with the required type signature does not exist in the given generic function. +A method with the required type signature does not exist in the given generic function. Alternatively, there is no unique most-specific method. """ MethodError diff --git a/base/essentials.jl b/base/essentials.jl index c66ed3ef28495..22802180d0e03 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -125,11 +125,11 @@ end map(f::Function, a::Array{Any,1}) = Any[ f(a[i]) for i=1:length(a) ] function precompile(f::ANY, args::Tuple) - ccall(:jl_compile_hint, Void, (Any,), Tuple{Core.Typeof(f), args...}) + ccall(:jl_compile_hint, Cint, (Any,), Tuple{Core.Typeof(f), args...}) != 0 end function precompile(argt::Type) - ccall(:jl_compile_hint, Void, (Any,), argt) + ccall(:jl_compile_hint, Cint, (Any,), argt) != 0 end esc(e::ANY) = Expr(:escape, e) diff --git a/base/reflection.jl b/base/reflection.jl index 41d3ef1fd910d..c88821357eb71 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -304,7 +304,7 @@ function _dump_function(f, t::ANY, native, wrapper, strip_ir_metadata, dump_modu llvmf = ccall(:jl_get_llvmf, Ptr{Void}, (Any, Bool, Bool), t, wrapper, native) if llvmf == C_NULL - error("no method found for the specified argument types") + error("did not find a unique method for the specified argument types") end if native diff --git a/base/replutil.jl b/base/replutil.jl index 03df5a35c1768..bb0c44c55f6da 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -166,9 +166,13 @@ function showerror(io::IO, ex::MethodError) # a tuple of the arguments otherwise. is_arg_types = isa(ex.args, DataType) arg_types = is_arg_types ? ex.args : typesof(ex.args...) + f = ex.f + meth = methods(f, arg_types) + if length(meth) > 1 + return showerror_ambiguous(io, meth, f, arg_types) + end arg_types_param::SimpleVector = arg_types.parameters print(io, "MethodError: ") - f = ex.f ft = typeof(f) name = ft.name.mt.name f_is_function = false @@ -247,6 +251,20 @@ end striptype{T}(::Type{T}) = T striptype(::Any) = nothing +function showerror_ambiguous(io::IO, meth, f, args) + print(io, "MethodError: ", f, "(") + p = args.parameters + for (i,a) in enumerate(p) + print(io, "::", a) + i == length(p) ? print(io, ")") : print(io, ", ") + end + print(io, " is ambiguous. Candidates:") + for m in meth + print(io, "\n ", m) + end + nothing +end + #Show an error by directly calling jl_printf. #Useful in Base submodule __init__ functions where STDERR isn't defined yet. function showerror_nostdio(err, msg::AbstractString) diff --git a/base/serialize.jl b/base/serialize.jl index 278d42a264fad..ab627c2e40fe3 100644 --- a/base/serialize.jl +++ b/base/serialize.jl @@ -316,6 +316,7 @@ function serialize(s::SerializationState, meth::Method) serialize(s, meth.line) serialize(s, meth.sig) serialize(s, meth.tvars) + serialize(s, meth.ambig) serialize(s, meth.isstaged) serialize(s, meth.lambda_template) if isdefined(meth, :roots) @@ -589,6 +590,7 @@ function deserialize(s::SerializationState, ::Type{Method}) line = deserialize(s) sig = deserialize(s) tvars = deserialize(s) + ambig = deserialize(s) isstaged = deserialize(s)::Bool template = deserialize(s)::LambdaInfo tag = Int32(read(s.io, UInt8)::UInt8) @@ -604,6 +606,7 @@ function deserialize(s::SerializationState, ::Type{Method}) meth.line = line meth.sig = sig meth.tvars = tvars + meth.ambig = ambig meth.isstaged = isstaged meth.lambda_template = template roots === nothing || (meth.roots = roots) diff --git a/src/alloc.c b/src/alloc.c index 7db4a6ec24d2f..cfe11991efda6 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -476,6 +476,7 @@ static jl_lambda_info_t *jl_copy_lambda(jl_lambda_info_t *linfo) new_linfo->nargs = linfo->nargs; new_linfo->isva = linfo->isva; new_linfo->rettype = linfo->rettype; + new_linfo->def = linfo->def; return new_linfo; } diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 2d790b5b56f43..021b811b7d195 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1417,7 +1417,7 @@ static Value *boxed(const jl_cgval_t &vinfo, jl_codectx_t *ctx, bool gcrooted) if (t == T_int1) return julia_bool(v); - if (ctx->linfo->def) { // don't bother codegen pre-boxing for toplevel + if (ctx->linfo && ctx->linfo->def) { // don't bother codegen pre-boxing for toplevel if (Constant *c = dyn_cast(v)) { jl_value_t *s = static_constant_instance(c, jt); if (s) { diff --git a/src/codegen.cpp b/src/codegen.cpp index fd2c3d18cfc34..68362e76dff3d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1099,11 +1099,10 @@ void *jl_get_llvmf(jl_tupletype_t *tt, bool getwrapper, bool getdeclarations) jl_typemap_entry_t *entry; linfo = jl_method_lookup_by_type( ((jl_datatype_t*)jl_tparam0(tt))->name->mt, tt, 0, 0, &entry); - if (linfo == NULL) { + if (linfo == NULL || jl_has_call_ambiguities(tt, linfo->def)) { JL_GC_POP(); return NULL; } - check_ambig_call(linfo->def, tt); } } if (linfo == NULL) { diff --git a/src/dump.c b/src/dump.c index bb7be8cbd474a..9a5e30d8156da 100644 --- a/src/dump.c +++ b/src/dump.c @@ -1446,7 +1446,7 @@ static jl_value_t *jl_deserialize_value_(ios_t *s, jl_value_t *vtag, jl_value_t m->tvars = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->tvars); jl_gc_wb(m, m->tvars); m->ambig = jl_deserialize_value(s, (jl_value_t**)&m->ambig); - jl_gc_wb(m, m->tvars); + jl_gc_wb(m, m->ambig); m->called = read_int8(s); m->module = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->module); jl_gc_wb(m, m->module); diff --git a/src/gf.c b/src/gf.c index 55c81fc58fa9c..9fd2e8a7efc17 100644 --- a/src/gf.c +++ b/src/gf.c @@ -697,12 +697,15 @@ static jl_lambda_info_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t * } jl_method_t *m = (*entry)->func.method; + if (jl_has_call_ambiguities(tt, m)) { + JL_GC_POP(); + return NULL; + } sig = join_tsig(tt, (*entry)->sig); jl_lambda_info_t *nf; if (!cache) nf = jl_get_specialized(m, sig, env); else { - check_ambig_call(m, tt); // if ambiguous, don't insert into cache nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, sig, tt, *entry, env); } JL_GC_POP(); @@ -889,8 +892,10 @@ void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletyp jl_value_t *oldvalue; jl_typemap_entry_t *newentry = jl_typemap_insert(&mt->defs, (jl_value_t*)mt, type, tvars, simpletype, jl_emptysvec, (jl_value_t*)method, 0, &method_defs, &oldvalue); - if (oldvalue) + if (oldvalue) { + method->ambig = ((jl_method_t*)oldvalue)->ambig; method_overwrite(newentry, (jl_method_t*)oldvalue); + } else check_ambiguous_matches(mt->defs, newentry); invalidate_conflicting(&mt->cache, (jl_value_t*)type, (jl_value_t*)mt); @@ -898,7 +903,7 @@ void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletyp JL_SIGATOMIC_END(); } -void JL_NORETURN jl_no_method_error_bare(jl_function_t *f, jl_value_t *args) +void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args) { jl_value_t *fargs[3] = { (jl_value_t*)jl_methoderror_type, @@ -919,11 +924,11 @@ void JL_NORETURN jl_no_method_error_bare(jl_function_t *f, jl_value_t *args) // not reached } -void JL_NORETURN jl_no_method_error(jl_function_t *f, jl_value_t **args, size_t na) +void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na) { jl_value_t *argtup = jl_f_tuple(NULL, args+1, na-1); JL_GC_PUSH1(&argtup); - jl_no_method_error_bare(f, argtup); + jl_method_error_bare(f, argtup); // not reached } @@ -1006,6 +1011,7 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim); // compile-time method lookup jl_lambda_info_t *jl_get_specialization1(jl_tupletype_t *types) { + jl_typemap_entry_t *entry = NULL; assert(jl_nparams(types) > 0); if (!jl_is_leaf_type((jl_value_t*)types)) return NULL; @@ -1031,17 +1037,19 @@ jl_lambda_info_t *jl_get_specialization1(jl_tupletype_t *types) // most of the time sf is rooted in mt, but if the method is staged it may // not be the case JL_GC_PUSH1(&sf); - jl_typemap_entry_t *entry; JL_TRY { sf = jl_method_lookup_by_type(mt, types, 1, 1, &entry); } JL_CATCH { goto not_found; } + if (sf != NULL) { + jl_method_t *m = sf->def; + if (jl_has_call_ambiguities(types, m)) { + goto not_found; + } + } if (sf == NULL || sf->code == NULL || sf->inInference) goto not_found; - jl_method_t *m = sf->def; - if (m->ambig != jl_nothing) - check_ambig_call(m, types); if (sf->functionObjectsDecls.functionObject == NULL) { if (sf->fptr != NULL) goto not_found; @@ -1054,22 +1062,22 @@ jl_lambda_info_t *jl_get_specialization1(jl_tupletype_t *types) return NULL; } -JL_DLLEXPORT void jl_compile_hint(jl_tupletype_t *types) +JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) { - (void)jl_get_specialization1(types); + return jl_get_specialization1(types) != NULL; } -void check_ambig_call(jl_method_t *m, jl_tupletype_t *types) +int jl_has_call_ambiguities(jl_tupletype_t *types, jl_method_t *m) { - if (m->ambig == jl_nothing) - return; + if (m->ambig == jl_nothing) return 0; for (size_t i = 0; i < jl_array_len(m->ambig); i++) { jl_method_t *mambig = (jl_method_t*)jl_cellref(m->ambig, i); if (jl_type_intersection((jl_value_t*)mambig->sig, (jl_value_t*)types) != (jl_value_t*)jl_bottom_type) { - jl_error("ambiguous"); + return 1; } } + return 0; } // add type of `f` to front of argument tuple type @@ -1528,10 +1536,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) show_call(F, args, nargs); #endif JL_GC_POP(); - jl_method_t *m = entry->func.method; - if (m->ambig != jl_nothing) - check_ambig_call(m, tt); - jl_no_method_error((jl_function_t*)F, args, nargs); + jl_method_error((jl_function_t*)F, args, nargs); // unreachable } } @@ -1588,7 +1593,7 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types); if ((jl_value_t*)entry == jl_nothing) { - jl_no_method_error_bare(gf, (jl_value_t*)types0); + jl_method_error_bare(gf, (jl_value_t*)types0); // unreachable } @@ -1709,8 +1714,8 @@ static int tvar_exists_at_top_level(jl_value_t *tv, jl_tupletype_t *sig, int att struct ml_matches_env { struct typemap_intersection_env match; - jl_value_t *t; - jl_svec_t *matc; + jl_value_t *t; // results: array of svec(argtypes, params, Method) + jl_svec_t *matc; // current working svec int lim; }; static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure0) @@ -1723,14 +1728,22 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio more generally, we can stop when the type is a subtype of the union of all the signatures examined so far. */ - assert(ml->func.linfo); + jl_method_t *meth = ml->func.method; + assert(meth); int skip = 0; size_t len = jl_array_len(closure->t); - if (closure->lim >= 0) { - // we can skip this match if the types are already covered - // by a prior (more specific) match. but only do this in - // the "limited" mode used by type inference. - for (i = 0; i < len; i++) { + // skip over any previously-added or less specific methods + // (this method might have been added previously through meth->ambig) + for (i = 0; i < len; i++) { + jl_method_t *priormeth = (jl_method_t*)jl_svecref(jl_cellref(closure->t, i), 2); + if (priormeth == meth) { + skip = 1; + break; + } + if (closure->lim >= 0) { + // we can skip this match if the types are already covered + // by a prior (more specific) match. but only do this in + // the "limited" mode used by type inference. jl_value_t *prior_ti = jl_svecref(jl_cellref(closure->t, i), 0); // in issue #13007 we incorrectly set skip=1 here, due to // Type{_<:T} ∩ (UnionAll S Type{T{S}}) = Type{T{S}} @@ -1759,7 +1772,31 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio closure->t = (jl_value_t*)jl_false; return 0; // terminate search } - closure->matc = jl_svec(3, closure->match.ti, closure->match.env, ml->func.method); + closure->matc = jl_svec(3, closure->match.ti, closure->match.env, meth); + if (len == 0) { + closure->t = (jl_value_t*)jl_alloc_cell_1d(1); + jl_cellset(closure->t, 0, (jl_value_t*)closure->matc); + } + else { + jl_cell_1d_push((jl_array_t*)closure->t, (jl_value_t*)closure->matc); + } + // Add ambiguous calls + if (meth->ambig != jl_nothing) { + jl_svec_t *env = NULL; + JL_GC_PUSH1(&env); + for (size_t j = 0; j < jl_array_len(meth->ambig); j++) { + jl_method_t *mambig = (jl_method_t*)jl_cellref(meth->ambig, j); + env = jl_emptysvec; + jl_value_t *mti = jl_type_intersection_matching((jl_value_t*)mambig->sig, + (jl_value_t*)closure->match.type, + &env, jl_emptysvec); + if (mti != (jl_value_t*)jl_bottom_type) { + jl_cell_1d_push((jl_array_t*)closure->t, + (jl_value_t*)jl_svec(3, mti, env, mambig)); + } + } + JL_GC_POP(); + } /* Check whether all static parameters matched. If not, then we have an argument type like Vector{T{Int,_}}, and a signature like @@ -1783,13 +1820,6 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio break; } } - if (len == 0) { - closure->t = (jl_value_t*)jl_alloc_cell_1d(1); - jl_cellset(closure->t, 0, (jl_value_t*)closure->matc); - } - else { - jl_cell_1d_push((jl_array_t*)closure->t, (jl_value_t*)closure->matc); - } // (type ∩ ml->sig == type) ⇒ (type ⊆ ml->sig) // NOTE: jl_subtype check added in case the intersection is // over-approximated. @@ -1801,10 +1831,11 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 1; } -// this is the collect form of calling jl_typemap_intersection_visitor -// with optimizations to skip fully shadowed methods +// This is the collect form of calling jl_typemap_intersection_visitor +// with optimizations to skip fully shadowed methods. // -// returns a match as and array of svec(argtypes, static_params, Method) +// Returns a match as an array of svec(argtypes, static_params, Method). +// See below for the meaning of lim. static jl_value_t *ml_matches(union jl_typemap_t defs, int offs, jl_tupletype_t *type, int lim) { diff --git a/src/julia.h b/src/julia.h index 7faa2de67ee7e..8f1d893ed5171 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1124,6 +1124,7 @@ JL_DLLEXPORT void jl_array_grow_beg(jl_array_t *a, size_t inc); JL_DLLEXPORT void jl_array_del_beg(jl_array_t *a, size_t dec); JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t sz); JL_DLLEXPORT void jl_cell_1d_push(jl_array_t *a, jl_value_t *item); +JL_DLLEXPORT void jl_cell_1d_push2(jl_array_t *a, jl_value_t *b, jl_value_t *c); JL_DLLEXPORT jl_value_t *jl_apply_array_type(jl_datatype_t *type, size_t dim); // property access JL_DLLEXPORT void *jl_array_ptr(jl_array_t *a); diff --git a/src/julia_internal.h b/src/julia_internal.h index 0856f19e4e81c..ec3a40fffa65f 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -139,8 +139,9 @@ void jl_set_t_uid_ctr(int i); uint32_t jl_get_gs_ctr(void); void jl_set_gs_ctr(uint32_t ctr); -void JL_NORETURN jl_no_method_error_bare(jl_function_t *f, jl_value_t *args); -void JL_NORETURN jl_no_method_error(jl_function_t *f, jl_value_t **args, size_t na); +void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args); +void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na); + JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t); #define JL_CALLABLE(name) \ @@ -216,7 +217,6 @@ jl_lambda_info_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *t jl_typemap_entry_t **entry); jl_lambda_info_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache, jl_typemap_entry_t **entry); jl_value_t *jl_gf_invoke(jl_tupletype_t *types, jl_value_t **args, size_t nargs); -void check_ambig_call(jl_method_t *m, jl_tupletype_t *types); jl_array_t *jl_lam_args(jl_expr_t *l); jl_array_t *jl_lam_vinfo(jl_expr_t *l); @@ -281,6 +281,8 @@ void jl_idtable_rehash(jl_array_t **pa, size_t newsz); JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *module); jl_lambda_info_t *jl_get_specialization1(jl_tupletype_t *types); +int jl_has_call_ambiguities(jl_tupletype_t *types, jl_method_t *m); + jl_function_t *jl_module_get_initializer(jl_module_t *m); uint32_t jl_module_next_counter(jl_module_t *m); void jl_fptr_to_llvm(jl_fptr_t fptr, jl_lambda_info_t *lam, int specsig); diff --git a/test/ambiguous.jl b/test/ambiguous.jl index ed2ebbe141d3c..05915a7537f9b 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -1,14 +1,14 @@ # DO NOT CHANGE LINE NUMBERS BELOW -@noinline foo(x, y) = 1 -@noinline foo(x::Integer, y) = 2 -@noinline foo(x, y::Integer) = 3 -@noinline foo(x::Int, y::Int) = 4 -@noinline foo(x::Number, y) = 5 +ambig(x, y) = 1 +ambig(x::Integer, y) = 2 +ambig(x, y::Integer) = 3 +ambig(x::Int, y::Int) = 4 +ambig(x::Number, y) = 5 # END OF LINE NUMBER SENSITIVITY ambigs = Any[[], [3], [2,5], [], [3]] -mt = methods(foo) +mt = methods(ambig) getline(m::Method) = m.line - 1 # -1 for the comment at the top @@ -23,8 +23,40 @@ for m in mt end end -@test foo("hi", "there") == 1 -@test foo(3.1, 3.2) == 5 -@test foo(3, 4) == 4 -@test_throws ErrorException foo(0x03, 4) -@test_throws ErrorException foo(0x03, 4) # test that not inserted into cache +@test length(methods(ambig, (Int, Int))) == 1 +@test length(methods(ambig, (UInt8, Int))) == 2 + +@test ambig("hi", "there") == 1 +@test ambig(3.1, 3.2) == 5 +@test ambig(3, 4) == 4 +@test_throws MethodError ambig(0x03, 4) +@test_throws MethodError ambig(0x03, 4) # test that not inserted into cache + +# Ensure it still works with potential inlining +callambig(x, y) = ambig(x, y) +@test_throws MethodError callambig(0x03, 4) + +## Other ways of accessing functions +# Test that non-ambiguous cases work +io = IOBuffer() +@test precompile(ambig, (Int, Int)) == true +cfunction(ambig, Int, (Int, Int)) +@test length(code_lowered(ambig, (Int, Int))) == 1 +@test length(code_typed(ambig, (Int, Int))) == 1 +code_llvm(io, ambig, (Int, Int)) +code_native(io, ambig, (Int, Int)) + +# Test that ambiguous cases fail appropriately +@test precompile(ambig, (UInt8, Int)) == false +cfunction(ambig, Int, (UInt8, Int)) # test for a crash (doesn't throw an error) +@test length(code_lowered(ambig, (UInt8, Int))) == 2 +@test length(code_typed(ambig, (UInt8, Int))) == 2 +@test_throws ErrorException code_llvm(io, ambig, (UInt8, Int)) +@test_throws ErrorException code_native(io, ambig, (UInt8, Int)) + +# Method overwriting doesn't destroy ambiguities +@test_throws MethodError ambig(2, 0x03) +ambig(x, y::Integer) = 3 +@test_throws MethodError ambig(2, 0x03) + +nothing From be8679e965d89baf10161112e44f594339cd7daf Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 3 May 2016 09:00:45 -0500 Subject: [PATCH 4/5] Add tools for testing for ambiguities --- base/reflection.jl | 8 ++++++++ base/test.jl | 42 ++++++++++++++++++++++++++++++++++++++ test/ambiguous.jl | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/base/reflection.jl b/base/reflection.jl index c88821357eb71..babd0fd577150 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -434,3 +434,11 @@ function method_exists(f::ANY, t::ANY) t = Tuple{isa(f,Type) ? Type{f} : typeof(f), t.parameters...} return ccall(:jl_method_exists, Cint, (Any, Any), typeof(f).name.mt, t) != 0 end + +function isambiguous(m1::Method, m2::Method) + ti = typeintersect(m1.sig, m2.sig) + ml = _methods_by_ftype(ti, 1) + ml == false && return true + m = ml[1][3] + !(m.sig <: ti) +end diff --git a/base/test.jl b/base/test.jl index f7631e7c04a26..79c60265471d3 100644 --- a/base/test.jl +++ b/base/test.jl @@ -17,6 +17,7 @@ export @test, @test_throws export @testset # Legacy approximate testing functions, yet to be included export @test_approx_eq, @test_approx_eq_eps, @inferred +export detect_ambiguities #----------------------------------------------------------------------- @@ -823,4 +824,45 @@ function test_approx_eq_modphase{S<:Real,T<:Real}( end end +""" + detect_ambiguities(mod1, mod2...; imported=false) + +Returns a vector of `(Method,Method)` pairs of ambiguous methods +defined in the specified modules. Use `imported=true` if you wish to +also test functions that were imported into these modules from +elsewhere. +""" +function detect_ambiguities(mods...; imported::Bool=false) + function sortdefs(m1, m2) + ord12 = m1.file < m2.file + if !ord12 && (m1.file == m2.file) + ord12 = m1.line < m2.line + end + ord12 ? (m1, m2) : (m2, m1) + end + ambs = Set{Tuple{Method,Method}}() + for mod in mods + for n in names(mod, true, imported) + try + f = getfield(mod, n) + if isa(f, Function) + mt = methods(f) + for m in mt + if m.ambig != nothing + for m2 in m.ambig + if Base.isambiguous(m, m2) + push!(ambs, sortdefs(m, m2)) + end + end + end + end + end + catch + println("Skipping ", mod, '.', n) # typically stale exports + end + end + end + collect(ambs) +end + end # module diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 05915a7537f9b..87d6aa2670306 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -59,4 +59,55 @@ cfunction(ambig, Int, (UInt8, Int)) # test for a crash (doesn't throw an error) ambig(x, y::Integer) = 3 @test_throws MethodError ambig(2, 0x03) +# Automatic detection of ambiguities +module Ambig1 + +ambig(x, y) = 1 +ambig(x::Integer, y) = 2 +ambig(x, y::Integer) = 3 + +end + +ambs = detect_ambiguities(Ambig1) +@test length(ambs) == 1 + +module Ambig2 + +ambig(x, y) = 1 +ambig(x::Integer, y) = 2 +ambig(x, y::Integer) = 3 +ambig(x::Number, y) = 4 + +end + +ambs = detect_ambiguities(Ambig2) +@test length(ambs) == 2 + +module Ambig3 + +ambig(x, y) = 1 +ambig(x::Integer, y) = 2 +ambig(x, y::Integer) = 3 +ambig(x::Int, y::Int) = 4 + +end + +ambs = detect_ambiguities(Ambig3) +@test length(ambs) == 1 + +module Ambig4 + +ambig(x, y) = 1 +ambig(x::Int, y) = 2 +ambig(x, y::Int) = 3 +ambig(x::Int, y::Int) = 4 + +end + +ambs = detect_ambiguities(Ambig4) +@test length(ambs) == 0 + +# Test that Core and Base are free of ambiguities +@test isempty(detect_ambiguities(Core, Base; imported=true)) + nothing From 76fa60466494f05fafae78b8751c0252515df955 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 4 May 2016 10:31:13 -0500 Subject: [PATCH 5/5] Add docs and NEWS --- NEWS.md | 18 ++++++++++++------ doc/stdlib/base.rst | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4f8c574bb0e29..d77efdbd58b36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -51,12 +51,10 @@ Compiler/Runtime improvements Breaking changes ---------------- - * Local variables and arguments are represented in lowered code as numbered `Slot` - objects instead of as symbols ([#15609]). - - * The information that used to be in the `ast` field of the `LambdaStaticData` type - is now divided among the fields `code`, `slotnames`, `slottypes`, `slotflags`, - `gensymtypes`, `rettype`, `nargs`, and `isva` in the `LambdaInfo` type ([#15609]). + * Method ambiguities no longer generate warnings when files are + loaded, nor do they dispatch to an arbitrarily-chosen method; + instead, a call that cannot be resolved to a single method results + in a `MethodError`. ([#6190]) * `pmap` keyword arguments `err_retry=true` and `err_stop=false` are deprecated. `pmap` no longer retries or returns `Exception` objects in the result collection. @@ -67,6 +65,13 @@ Breaking changes If a reshaped copy is needed, use `copy(reshape(a))` or `copy!` to a new array of the desired shape ([#4211]). + * Local variables and arguments are represented in lowered code as numbered `Slot` + objects instead of as symbols ([#15609]). + + * The information that used to be in the `ast` field of the `LambdaStaticData` type + is now divided among the fields `code`, `slotnames`, `slottypes`, `slotflags`, + `gensymtypes`, `rettype`, `nargs`, and `isva` in the `LambdaInfo` type ([#15609]). + Library improvements -------------------- @@ -206,3 +211,4 @@ Deprecated or removed [#15550]: https://github.com/JuliaLang/julia/issues/15550 [#15609]: https://github.com/JuliaLang/julia/issues/15609 [#15763]: https://github.com/JuliaLang/julia/issues/15763 +[#6190]: https://github.com/JuliaLang/julia/issues/6190 diff --git a/doc/stdlib/base.rst b/doc/stdlib/base.rst index cfe5fe679b4d4..5d47c1f0f2dfa 100644 --- a/doc/stdlib/base.rst +++ b/doc/stdlib/base.rst @@ -1144,7 +1144,7 @@ Errors .. Docstring generated from Julia source - A method with the required type signature does not exist in the given generic function. + A method with the required type signature does not exist in the given generic function. Alternatively, there is no unique most-specific method. .. function:: NullException()