diff --git a/baml_language/crates/baml_compiler2_hir_ty/src/diagnostics.rs b/baml_language/crates/baml_compiler2_hir_ty/src/diagnostics.rs index 334a9ae6647..9ff16892e73 100644 --- a/baml_language/crates/baml_compiler2_hir_ty/src/diagnostics.rs +++ b/baml_language/crates/baml_compiler2_hir_ty/src/diagnostics.rs @@ -446,7 +446,17 @@ pub enum TirTypeError { /// (`let f = identity` where `identity`). A generic function is a type /// constructor — it must be specialized (`identity`) or have its type /// arguments inferable from context before it becomes a usable value. - GenericFunctionValueNotSpecialized { name: Name }, + GenericFunctionValueNotSpecialized { + name: Name, + reference: String, + had_expected_type: bool, + generic_params: Vec, + binding_name: Option, + function_shape: Option, + annotation_example: Option, + specialization_example: Option, + specialization_syntax_available: bool, + }, /// A lambda parameter has no type annotation and no expected type context /// to infer the type from. CannotInferLambdaParamType { param_name: Name }, @@ -911,6 +921,84 @@ pub enum TirTypeError { }, } +#[allow(clippy::too_many_arguments)] +fn write_generic_function_value_diagnostic( + f: &mut fmt::Formatter<'_>, + name: &Name, + reference: &str, + had_expected_type: bool, + generic_params: &[Name], + binding_name: Option<&Name>, + function_shape: Option<&str>, + annotation_example: Option<&str>, + specialization_example: Option<&str>, + specialization_syntax_available: bool, +) -> fmt::Result { + if had_expected_type { + write!( + f, + "cannot determine every type argument for generic function `{name}` from the expected function type. " + )?; + } else if let Some(binding) = binding_name { + write!( + f, + "generic function `{name}` needs concrete type arguments before it can be stored in `{binding}`. " + )?; + } else { + write!( + f, + "generic function `{name}` needs concrete type arguments before it can be used as a value. " + )?; + } + + let param_names = generic_params + .iter() + .map(Name::as_str) + .collect::>() + .join(", "); + if let Some(example_args) = specialization_example { + write!( + f, + "Specialize it explicitly, for example `{reference}<{example_args}>`. " + )?; + } else if !specialization_syntax_available { + write!( + f, + "This reference form cannot take type arguments as a stored value; choose concrete types for {param_names} with a function type annotation instead. " + )?; + } else { + write!( + f, + "Specialize it explicitly by choosing concrete types for {param_names}; each type must satisfy its declared bounds. " + )?; + } + + match (binding_name, annotation_example, function_shape) { + (Some(binding), Some(example), _) => write!( + f, + "Or write the concrete function type after the binding name: `let {binding}: {example} = {reference}`. " + )?, + (Some(binding), None, Some(shape)) => write!( + f, + "Or write the concrete function type after the binding name, using `{shape}` as the shape and replacing {param_names} with the types you want: `let {binding}: ... = {reference}`. " + )?, + (None, Some(example), _) => write!( + f, + "Bind it first with a concrete function type: `let function_value: {example} = {reference}`. " + )?, + (_, _, Some(shape)) => write!( + f, + "A concrete annotation can use `{shape}` as its shape; replace {param_names} with the types you want. " + )?, + _ => {} + } + + write!( + f, + "Calling `{reference}(...)` directly works only when that call's arguments or expected result determine every type argument" + ) +} + impl fmt::Display for TirTypeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -1478,13 +1566,50 @@ impl fmt::Display for TirTypeError { "{kind} `{type_name}` is not generic and cannot take type arguments" ) } - TirTypeError::GenericFunctionValueNotSpecialized { name } => { - write!( - f, - "generic function `{name}` must be specialized before it is used \ - as a value (e.g. `{name}`)" - ) - } + TirTypeError::GenericFunctionValueNotSpecialized { + name, + reference, + had_expected_type: true, + generic_params, + binding_name, + function_shape, + annotation_example, + specialization_example, + specialization_syntax_available, + } => write_generic_function_value_diagnostic( + f, + name, + reference, + true, + generic_params, + binding_name.as_ref(), + function_shape.as_deref(), + annotation_example.as_deref(), + specialization_example.as_deref(), + *specialization_syntax_available, + ), + TirTypeError::GenericFunctionValueNotSpecialized { + name, + reference, + had_expected_type: false, + generic_params, + binding_name, + function_shape, + annotation_example, + specialization_example, + specialization_syntax_available, + } => write_generic_function_value_diagnostic( + f, + name, + reference, + false, + generic_params, + binding_name.as_ref(), + function_shape.as_deref(), + annotation_example.as_deref(), + specialization_example.as_deref(), + *specialization_syntax_available, + ), TirTypeError::TypeParamShadowed { param_name, type_name, diff --git a/baml_language/crates/baml_compiler2_hir_ty/src/infer.rs b/baml_language/crates/baml_compiler2_hir_ty/src/infer.rs index 811c7969301..588cb3c76fa 100644 --- a/baml_language/crates/baml_compiler2_hir_ty/src/infer.rs +++ b/baml_language/crates/baml_compiler2_hir_ty/src/infer.rs @@ -798,6 +798,21 @@ enum PendingDiag<'db> { expected: usize, got: usize, }, + GenericFunctionValueNotSpecialized { + expr: ExprId, + name: baml_type::Name, + reference: String, + inference_evidence: Vec, + specialization_args: Option>, + unconditional: bool, + had_expected_type: bool, + generic_params: Vec, + binding_name: Option, + function_shape: Option, + annotation_ty: Option, + specialization_example_is_safe: bool, + specialization_syntax_available: bool, + }, ComputedGenericArgumentRequiresUnreflect { expr: ExprId, name: baml_type::Name, @@ -1700,6 +1715,11 @@ struct InferenceContext<'db> { /// discipline): a failed lookup reports only when no fallback tier /// remains - probes increment, the committed frame reports. member_probe_depth: u32, + /// Nonzero while an optional-call callee is inferred as an ordinary + /// expression. Its arguments still provide specialization evidence after + /// the callee has been typed, so a generic value diagnostic must remain + /// conditional until the whole call has been checked. + optional_call_callee_depth: u32, /// Depth of pattern lowering where the dead-pattern overlap check /// probes SILENTLY: or-pattern alternatives (one alt that can't /// match is fine - rustc's rule - only the whole `|` chain failing @@ -1839,6 +1859,7 @@ impl<'db> InferenceContext<'db> { annotation_cache: FxHashMap::default(), canonical_cache: baml_type::normalize::InternedCanonicalCache::default(), member_probe_depth: 0, + optional_call_callee_depth: 0, or_probe_depth: 0, rest_reject_depth: 0, template_params: Vec::new(), @@ -2278,7 +2299,7 @@ impl<'db> InferenceContext<'db> { Expr::ByteStringLiteral(_) => Ty::intern(TyKind::Uint8Array { attr: TyAttr::default(), }), - Expr::Path(segments) => self.resolve_value_path(expr, segments), + Expr::Path(segments) => self.resolve_value_path(body, expr, segments, expected), Expr::Index { base, index } => self.infer_index(body, expr, *base, *index, false), Expr::Spawn { name, @@ -2850,7 +2871,9 @@ impl<'db> InferenceContext<'db> { } Expr::OptionalCall { callee, args } => { self.validate_runtime_type_arg_operands(body, expr); + self.optional_call_callee_depth += 1; let callee_ty = self.infer_expr(body, *callee, &Expectation::None); + self.optional_call_callee_depth -= 1; self.report_mounted_reserved_call(expr, *callee); self.check_needless_chain(body, expr, *callee, &callee_ty); let nonnull = self.peel_chain_null(&callee_ty); @@ -2887,6 +2910,7 @@ impl<'db> InferenceContext<'db> { Ty::error() } }; + self.report_unspecialized_generic_method_value(body, expr, expected, &ty); self.result.type_of_expr.insert(expr, ty.clone()); ty } @@ -3573,6 +3597,21 @@ impl<'db> InferenceContext<'db> { fn sub(&mut self, actual: &Ty, expected: &Ty) -> bool { let mut actual = self.table.shallow_resolve(actual); let mut expected = self.table.shallow_resolve(expected); + // A function-type alias used as contextual type information must + // expose its parameter/return slots so they can determine a generic + // function value's instantiation (`let f: StringCallback = identity`). + // Keep the normalization shape-directed: expanding every alias here + // would erase nominal alias identity from unrelated diagnostics. + if matches!(actual.kind(), TyKind::Function { .. }) + && matches!(expected.kind(), TyKind::TypeAlias(..)) + { + expected = self.expand_alias_ty(&expected); + } + if matches!(actual.kind(), TyKind::TypeAlias(..)) + && matches!(expected.kind(), TyKind::Function { .. }) + { + actual = self.expand_alias_ty(&actual); + } // Normalize-then-relate (rustc's FnCtxt normalize-before-unify; // r-a's `normalize_projection_ty` during unification): a GROUND // projection the oracle can already determine reduces before the @@ -3653,6 +3692,26 @@ impl<'db> InferenceContext<'db> { } ok } + // A var-carrying value flowing into a GROUND union can use a + // single structurally compatible arm as context. This is the + // optional-callback case: a function can only inhabit the + // function arm of `Callback | null`, so that arm may determine + // its generic slots. Multiple compatible arms remain ambiguous + // and stay deferred (`Fn | Fn` must not guess). + (_, TyKind::Union(members, _)) if actual.has_infer() && !expected.has_infer() => { + let targets: Vec = members + .iter() + .map(|member| self.expand_alias_ty(member)) + .filter(|member| same_head_constructor(&actual, member)) + .collect(); + if let [target] = targets.as_slice() { + let target = target.clone(); + return self.sub(&actual, &target); + } + self.deferred_subs + .push((actual, expected, self.obligation_anchor)); + true + } // A var-carrying union TARGET: TypeScript's // `inferToMultipleTypes`, the union-position inference rule // (`int | null <: ?R | null` must conclude `?R := int`; @@ -6475,14 +6534,133 @@ impl<'db> InferenceContext<'db> { .then(|| crate::method_resolution::instantiate_external_signature(&function, &[target])) } + /// Generic methods follow the same realization rule as free functions: + /// a bare value needs either explicit type arguments or enough contextual + /// function type information to determine them. Direct method calls do not + /// reach this path (their call site owns inference), and `GenericApply` + /// already carries an explicit specialization. + fn report_unspecialized_generic_method_value( + &mut self, + body: &ExprBody, + expr: ExprId, + expected: &Expectation, + inferred: &Ty, + ) { + if matches!(body.exprs[expr], Expr::GenericApply { .. }) { + return; + } + let Some(resolution) = self.result.member_resolutions.get(&expr).cloned() else { + return; + }; + let reference = body.display_expr(expr); + let had_context = expected.only_has_type().is_some() || self.optional_call_callee_depth > 0; + let source_method = match resolution { + MemberResolution::BoundMethod { func, .. } + | MemberResolution::InterfaceConcreteMethod { func, .. } => Some((func, true)), + MemberResolution::UnboundMethod { func, .. } => Some((func, false)), + MemberResolution::InterfaceVirtualMethod { interface, method } => { + baml_compiler2_ppir::item_data::interface_data(self.db, interface) + .methods + .iter() + .copied() + .find(|func| { + baml_compiler2_ppir::item_data::function_data(self.db, *func).name == method + }) + .map(|func| (func, true)) + } + MemberResolution::External(external) + if !matches!( + external.target, + crate::callable::ExternalCallTarget::Free { .. } + ) => + { + if external.user_generic_params().next().is_some() { + let generic_params: Vec<_> = external + .user_generic_params() + .map(|(param, _)| param.name().clone()) + .collect(); + let specialization_example_is_safe = external + .user_generic_params() + .all(|(_, bounds)| bounds.is_empty()); + let specialization_syntax_available = matches!(body.exprs[expr], Expr::Path(_)); + self.pending_diags + .push(PendingDiag::GenericFunctionValueNotSpecialized { + expr, + name: external.display_name().clone(), + reference, + inference_evidence: vec![inferred.clone()], + specialization_args: None, + unconditional: !had_context, + had_expected_type: had_context, + generic_params, + binding_name: initializer_binding_name(body, expr), + function_shape: None, + annotation_ty: None, + specialization_example_is_safe, + specialization_syntax_available, + }); + } + return; + } + _ => None, + }; + let Some((method, receiver_is_bound)) = source_method else { + return; + }; + let data = baml_compiler2_ppir::item_data::function_data(self.db, method); + if data.generic_params.is_empty() { + return; + } + let signature = function_signature(self.db, method); + let user_params = function_user_generic_params(self.db, method, signature); + let generic_params = user_params + .iter() + .map(|param| param.name().clone()) + .collect(); + let specialization_example_is_safe = data + .generic_params + .iter() + .all(|param| param.bounds.is_empty()); + let specialization_syntax_available = matches!(body.exprs[expr], Expr::Path(_)); + let has_phantom_param = user_params + .iter() + .any(|param| !function_signature_mentions_param(signature, param)); + self.pending_diags + .push(PendingDiag::GenericFunctionValueNotSpecialized { + expr, + name: data.name.clone(), + reference, + inference_evidence: vec![inferred.clone()], + specialization_args: None, + unconditional: !had_context || has_phantom_param, + had_expected_type: had_context, + generic_params, + binding_name: initializer_binding_name(body, expr), + function_shape: (!has_phantom_param).then(|| { + generic_function_value_shape(signature, user_params, receiver_is_bound, false) + }), + annotation_ty: (specialization_example_is_safe && !has_phantom_param) + .then(|| inferred.clone()), + specialization_example_is_safe, + specialization_syntax_available, + }); + } + /// The one home for value-position path typing (rust-analyzer's /// `infer/path.rs` shape): a local/parameter root followed by field /// accesses, or a package-level FUNCTION as a first-class value (`let c: /// (x: int) -> int throws never = inc;`), instantiated with fresh - /// variables per generic param - only a call site's turbofish can spell - /// arguments explicitly, and the expectation's bounds resolve them here. + /// variables per generic param. A contextual function type may resolve + /// those variables; without one, a generic function must be explicitly + /// specialized before it can become a value. /// Constants and enum variants join as later slices land. - fn resolve_value_path(&mut self, expr: ExprId, segments: &[baml_type::Name]) -> Ty { + fn resolve_value_path( + &mut self, + body: &ExprBody, + expr: ExprId, + segments: &[baml_type::Name], + expected: &Expectation, + ) -> Ty { if segments.len() == 1 && segments[0].as_str() == "$id" { return Ty::string(); } @@ -6524,16 +6702,73 @@ impl<'db> InferenceContext<'db> { if let Some(baml_compiler2_hir::contributions::Definition::Function(function)) = self.lower.resolve_value(segments) { + let had_context = + expected.only_has_type().is_some() || self.optional_call_callee_depth > 0; let signature = function_signature(self.db, function); let instantiation: Vec = signature .generic_params .iter() .map(|param| self.fresh_generic_arg(param)) .collect(); + let data = baml_compiler2_ppir::item_data::function_data(self.db, function); + if data.generic_params.is_empty() { + // Synthetic callback-effect parameters are inference-only; + // they do not make an otherwise non-generic function value + // require explicit specialization. + } else { + let user_params = function_user_generic_params(self.db, function, signature); + let generic_params = user_params + .iter() + .map(|param| param.name().clone()) + .collect(); + let specialization_example_is_safe = data + .generic_params + .iter() + .all(|param| param.bounds.is_empty()); + let has_phantom_param = user_params + .iter() + .any(|param| !function_signature_mentions_param(signature, param)); + let inference_evidence = user_params + .iter() + .filter_map(|param| instantiation.get(param.index() as usize).cloned()) + .collect(); + self.pending_diags + .push(PendingDiag::GenericFunctionValueNotSpecialized { + expr, + name: data.name.clone(), + reference: segments + .iter() + .map(baml_type::Name::as_str) + .collect::>() + .join("."), + inference_evidence, + specialization_args: Some( + user_params + .iter() + .filter_map(|param| { + instantiation.get(param.index() as usize).cloned() + }) + .collect(), + ), + unconditional: !had_context, + had_expected_type: had_context, + generic_params, + binding_name: initializer_binding_name(body, expr), + function_shape: (!has_phantom_param).then(|| { + generic_function_value_shape(signature, user_params, false, false) + }), + annotation_ty: (specialization_example_is_safe && !has_phantom_param) + .then(|| function_value_ty(signature, &instantiation)), + specialization_example_is_safe, + specialization_syntax_available: true, + }); + } self.write_member_resolution(expr, MemberResolution::Free { func: function }); return function_value_ty(signature, &instantiation); } if let Some(function) = self.lower.resolve_exported_value(segments) { + let had_context = + expected.only_has_type().is_some() || self.optional_call_callee_depth > 0; let external = function .external .clone() @@ -6543,6 +6778,63 @@ impl<'db> InferenceContext<'db> { .iter() .map(|param| self.fresh_generic_arg(param)) .collect(); + if external.user_generic_params().next().is_some() { + let user_params: Vec<_> = external + .user_generic_params() + .map(|(param, _)| param.clone()) + .collect(); + let generic_params = user_params + .iter() + .map(|param| param.name().clone()) + .collect(); + let specialization_example_is_safe = external + .user_generic_params() + .all(|(_, bounds)| bounds.is_empty()); + let shape_ty = + external_generic_function_value_ty(&function, &user_params, false, false); + let has_phantom_param = user_params + .iter() + .any(|param| !ty_mentions_param(&shape_ty, param)); + let inference_evidence = external + .user_generic_params() + .filter_map(|(param, _)| instantiation.get(param.index() as usize).cloned()) + .collect(); + self.pending_diags + .push(PendingDiag::GenericFunctionValueNotSpecialized { + expr, + name: function.name.clone(), + reference: segments + .iter() + .map(baml_type::Name::as_str) + .collect::>() + .join("."), + inference_evidence, + specialization_args: Some( + user_params + .iter() + .filter_map(|param| { + instantiation.get(param.index() as usize).cloned() + }) + .collect(), + ), + unconditional: !had_context, + had_expected_type: had_context, + generic_params, + binding_name: initializer_binding_name(body, expr), + function_shape: (!has_phantom_param) + .then(|| shape_ty.to_plain().to_string()), + annotation_ty: (specialization_example_is_safe && !has_phantom_param).then( + || { + crate::method_resolution::instantiate_external_signature( + &function, + &instantiation, + ) + }, + ), + specialization_example_is_safe, + specialization_syntax_available: true, + }); + } self.write_member_resolution(expr, MemberResolution::External(external)); return crate::method_resolution::instantiate_external_signature( &function, @@ -10925,6 +11217,64 @@ impl<'db> InferenceContext<'db> { }, expr, ), + PendingDiag::GenericFunctionValueNotSpecialized { + expr, + name, + reference, + inference_evidence, + specialization_args, + unconditional, + had_expected_type, + generic_params, + binding_name, + function_shape, + annotation_ty, + specialization_example_is_safe, + specialization_syntax_available, + } => { + let has_unresolved_user_arg = inference_evidence + .iter() + .any(|arg| self.finalize_ty(arg).has_error()); + if !unconditional && !has_unresolved_user_arg { + continue; + } + let specialization_example = if specialization_example_is_safe + && specialization_syntax_available + { + let mut args = Vec::with_capacity(generic_params.len()); + if let Some(specialization_args) = specialization_args { + for arg in &specialization_args { + let finalized = self.finalize_ty(arg); + args.push( + diagnostic_example_ty(&finalized).to_plain().to_string(), + ); + } + } else { + args.resize(generic_params.len(), "int".to_string()); + } + Some(args.join(", ")) + } else { + None + }; + let annotation_example = annotation_ty.map(|ty| { + let finalized = self.finalize_ty(&ty); + diagnostic_example_ty(&finalized).to_plain().to_string() + }); + ( + TirTypeError::GenericFunctionValueNotSpecialized { + name, + reference, + had_expected_type, + generic_params, + binding_name, + function_shape, + annotation_example, + specialization_example, + specialization_syntax_available, + }, + expr, + ) + } PendingDiag::ComputedGenericArgumentRequiresUnreflect { expr, name } => ( TirTypeError::ComputedGenericArgumentRequiresUnreflect { name }, expr, @@ -12486,6 +12836,39 @@ fn ty_mentions_param(ty: &Ty, param: &baml_type::ParamTy) -> bool { found } +/// The user-written portion of a function's flattened generic frame. Owner +/// parameters precede it and compiler-created callback-effect parameters +/// follow it, so neither group should make a function value require explicit +/// specialization. +fn function_user_generic_params<'a, 'db>( + db: &'db dyn baml_compiler2_ppir::Db, + function: baml_compiler2_hir::loc::FunctionLoc<'db>, + signature: &'a crate::lower::FunctionSignature, +) -> &'a [baml_type::ParamTy] { + let data = baml_compiler2_ppir::item_data::elaborated_function_data(db, function); + let end = signature + .generic_params + .len() + .checked_sub(data.synthetic_effect_params.len()) + .expect("synthetic effect parameters are a suffix of the generic frame"); + let start = end + .checked_sub(data.user_generic_params.len()) + .expect("user parameters precede synthetic effects in the generic frame"); + &signature.generic_params[start..end] +} + +fn function_signature_mentions_param( + signature: &crate::lower::FunctionSignature, + param: &baml_type::ParamTy, +) -> bool { + signature + .params + .iter() + .any(|function_param| ty_mentions_param(&function_param.ty, param)) + || ty_mentions_param(&signature.ret, param) + || ty_mentions_param(&signature.throws, param) +} + fn external_bounds_map( external: &crate::callable::ExternalCallable, ) -> FxHashMap> { @@ -12761,6 +13144,90 @@ fn function_value_ty(signature: &crate::lower::FunctionSignature, instantiation: }) } +fn generic_function_value_shape( + signature: &crate::lower::FunctionSignature, + user_params: &[baml_type::ParamTy], + receiver_is_bound: bool, + concrete_example: bool, +) -> String { + let mut instantiation: Vec = signature + .generic_params + .iter() + .map(|param| Ty::intern(TyKind::TypeVar(param.clone(), baml_type::TyAttr::default()))) + .collect(); + if concrete_example { + for param in user_params { + if let Some(slot) = instantiation.get_mut(param.index() as usize) { + *slot = Ty::int(); + } + } + } + let ty = function_value_ty(signature, &instantiation); + let ty = if receiver_is_bound { + bind_receiver(ty) + } else { + ty + }; + ty.to_plain().to_string() +} + +fn external_generic_function_value_ty( + function: &crate::package_interface::ResolvedFunction, + user_params: &[baml_type::ParamTy], + receiver_is_bound: bool, + concrete_example: bool, +) -> Ty { + let mut instantiation: Vec = function + .generic_params + .iter() + .map(|param| Ty::intern(TyKind::TypeVar(param.clone(), baml_type::TyAttr::default()))) + .collect(); + if concrete_example { + for param in user_params { + if let Some(slot) = instantiation.get_mut(param.index() as usize) { + *slot = Ty::int(); + } + } + } + let ty = crate::method_resolution::instantiate_external_signature(function, &instantiation); + if receiver_is_bound { + bind_receiver(ty) + } else { + ty + } +} + +fn initializer_binding_name(body: &ExprBody, initializer: ExprId) -> Option { + body.stmts.iter().find_map(|(_, stmt)| { + let Stmt::Let { + pattern, + initializer: Some(candidate), + .. + } = stmt + else { + return None; + }; + if *candidate != initializer { + return None; + } + match &body.patterns[*pattern] { + Pattern::Bind { name, .. } => Some(name.clone()), + _ => None, + } + }) +} + +/// Turn a finalized, partially inferred type into a concrete diagnostic +/// example without discarding the slots inference did solve. `int` is only a +/// placeholder for still-unknown, unbounded slots; bounded generics never use +/// this example path. +fn diagnostic_example_ty(ty: &Ty) -> Ty { + match ty.kind() { + TyKind::Error { .. } | TyKind::Infer { .. } => Ty::int(), + kind => Ty::intern(kind.map_children(diagnostic_example_ty)), + } +} + /// Replaces every `Infer` node (unsolved variable or hole) with the Error /// sentinel, in place - the finalize half of rulings 2/3. /// diff --git a/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/callback_effects_and_methods.baml b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/callback_effects_and_methods.baml new file mode 100644 index 00000000000..c853de70ab4 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/callback_effects_and_methods.baml @@ -0,0 +1,149 @@ +// `foo` invokes its callback, so the callback's concrete effect is forwarded +// through `foo` and must be admitted by its caller. +test "invoked generic callback effect propagates through wrapper" { + root.compiler.AssertCompiles( + { + "case.baml": ` + function fail_zero() -> T throws string { throw "boom" } + function foo(cb: () -> string) -> string { cb() } + function demo() -> string throws string { foo(fail_zero) } + `, + }, + ) +} + +test "invoked callback effect is rejected by a closed caller" { + root.compiler.AssertRejected( + { + "case.baml": ` + function fail_zero() -> T throws string { throw "boom" } + function foo(cb: () -> string) -> string { cb() } + function demo() -> string throws never { foo(fail_zero) } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0096", + message: "declared throws is `never`, but this function may also throw `string`", + }, + ) +} + +// Merely receiving a callback does not propagate its effect. Calling it under +// a catch also closes the effect before returning from the wrapper. +test "unused and caught callback effects do not escape" { + root.compiler.AssertCompiles( + { + "case.baml": ` + function fail_zero() -> T throws string { throw "boom" } + function ignores(cb: () -> string) -> string { "ok" } + function catches(cb: () -> string) -> string { + cb() catch_all (error) { _ => "fallback" } + } + function ignored() -> string throws never { ignores(fail_zero) } + function caught() -> string throws never { catches(fail_zero) } + `, + }, + ) +} + +test "explicit throws never callback rejects a throwing generic function" { + root.compiler.AssertRejected( + { + "case.baml": ` + function fail_zero() -> T throws string { throw "boom" } + function requires_never(cb: () -> string throws never) -> string { cb() } + function demo() -> string throws never { requires_never(fail_zero) } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "mismatched types: expected `() -> string throws never`, found `() -> string throws string`", + }, + ) +} + +test "generic method values follow the same realization rule" { + root.compiler.AssertRejected( + { + "case.baml": ` + class Box { + function same(self, value: T) -> T { value } + } + function demo(box: Box) -> string[] { + let copy = box.same; + let number = copy(7); + let word = copy("eight"); + [number.to_string(), word] + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `same` needs concrete type arguments before it can be stored in `copy`. Specialize it explicitly, for example `box.same`. Or write the concrete function type after the binding name: `let copy: (int) -> int throws never = box.same`. Calling `box.same(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +test "generic method value can specialize from callback context" { + root.compiler.AssertCompiles( + { + "case.baml": ` + type StringCallback = (string) -> string throws never + class Box { + function same(self, value: T) -> T { value } + } + function takes_string(cb: StringCallback) -> string { cb("ok") } + function demo(box: Box) -> string { takes_string(box.same) } + `, + }, + ) +} + +// A qualified interface projection is callable with method type arguments, +// but the standalone value form cannot spell `<...>`. Its diagnostic must not +// recommend syntax the parser rejects; the concrete annotation is the fix. +test "qualified method value recommends its available annotation spelling" { + root.compiler.AssertRejected( + { + "case.baml": ` + interface Embosser { + function emboss(u: U) -> string throws never + } + class Plate { + implements Embosser { + function emboss(u: U) -> string throws never { "ok" } + } + } + function demo() -> string { + let stamp = (Plate as Embosser).emboss; + stamp(7) + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `emboss` needs concrete type arguments before it can be stored in `stamp`. This reference form cannot take type arguments as a stored value; choose concrete types for U with a function type annotation instead. Or write the concrete function type after the binding name: `let stamp: (int) -> string throws never = (Plate as Embosser).emboss`. Calling `(Plate as Embosser).emboss(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +test "qualified method value compiles with a concrete annotation" { + root.compiler.AssertCompiles( + { + "case.baml": ` + interface Embosser { + function emboss(u: U) -> string throws never + } + class Plate { + implements Embosser { + function emboss(u: U) -> string throws never { "ok" } + } + } + function demo() -> string { + let stamp: (int) -> string throws never = (Plate as Embosser).emboss; + stamp(7) + } + `, + }, + ) +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/contextual_specialization.baml b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/contextual_specialization.baml new file mode 100644 index 00000000000..272b699b8a5 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/contextual_specialization.baml @@ -0,0 +1,117 @@ +test "explicit specialization and concrete annotation compile" { + root.compiler.AssertCompiles( + { + "case.baml": ` + type StringCallback = (string) -> string throws never + function identity(value: T) -> T { value } + + function explicit() -> string { + let copy = identity; + copy("ok") + } + + function annotated() -> string { + let copy: StringCallback = identity; + copy("ok") + } + `, + }, + ) +} + +test "direct calls may infer a fresh type on every call" { + root.compiler.AssertCompiles( + { + "case.baml": ` + function identity(value: T) -> T { value } + function direct_calls() -> string[] { + let number = identity(7); + let word = identity("eight"); + [number.to_string(), word] + } + `, + }, + ) +} + +// Context may come from any actual value slot: a callback argument, return, +// container element, object field, branch, default value, or optional arm. +test "concrete value contexts specialize a generic function" { + root.compiler.AssertCompiles( + { + "case.baml": ` + type StringCallback = (string) -> string throws never + class CallbackHolder { cb StringCallback } + + function identity(value: T) -> T { value } + function takes_string(cb: StringCallback) -> string { cb("ok") } + function takes_optional(cb: StringCallback?) -> string { "ok" } + function with_default(cb: StringCallback = identity) -> string { cb("ok") } + + function as_argument() -> string { takes_string(identity) } + function as_return() -> StringCallback throws never { identity } + function as_array() -> StringCallback[] { [identity] } + function as_map() -> map { { "id": identity } } + function as_field() -> CallbackHolder { CallbackHolder { cb: identity } } + function as_branch(flag: bool) -> StringCallback throws never { + if (flag) { identity } else { identity } + } + function as_optional_arm() -> string { takes_optional(identity) } + `, + }, + ) +} + +// Inference can use a sibling argument in the same call, but it cannot travel +// backward from a later use and turn an existing local into a generic binding. +test "a later sibling call argument may provide context" { + root.compiler.AssertCompiles( + { + "case.baml": ` + function identity(value: T) -> T { value } + function apply(cb: (T) -> T throws never, value: T) -> T { cb(value) } + function demo() -> int { apply(identity, 1) } + `, + }, + ) +} + +test "a later use cannot retroactively specialize a stored value" { + root.compiler.AssertRejected( + { + "case.baml": ` + type StringCallback = (string) -> string throws never + function identity(value: T) -> T { value } + function takes_string(cb: StringCallback) -> string { cb("ok") } + function demo() -> string { + let copy = identity; + takes_string(copy) + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `identity` needs concrete type arguments before it can be stored in `copy`. Specialize it explicitly, for example `identity`. Or write the concrete function type after the binding name: `let copy: (string) -> string throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// Both function arms are plausible, so a union does not provide one concrete +// function type to specialize against. +test "ambiguous callback union does not guess a specialization" { + root.compiler.AssertRejected( + { + "case.baml": ` + type StringCallback = (string) -> string throws never + type IntCallback = (int) -> int throws never + function identity(value: T) -> T { value } + function accepts(cb: StringCallback | IntCallback) -> bool { true } + function demo() -> bool { accepts(identity) } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "cannot determine every type argument for generic function `identity` from the expected function type. Specialize it explicitly, for example `identity`. Bind it first with a concrete function type: `let function_value: (int) -> int throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/diagnostics.baml b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/diagnostics.baml new file mode 100644 index 00000000000..77786597471 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_generics/ns_function_values/diagnostics.baml @@ -0,0 +1,129 @@ +// The main regression: a local binding is monomorphic. Later calls cannot +// independently specialize the same stored generic function value. +test "bare generic function binding reports both concrete fixes" { + root.compiler.AssertRejected( + { + "case.baml": ` + function identity(value: T) -> T { value } + + function polymorphic_value_examples() -> string[] { + let copy = identity; + let number = copy(7); + let word = copy("eight"); + [number.to_string(), word] + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `identity` needs concrete type arguments before it can be stored in `copy`. Specialize it explicitly, for example `identity`. Or write the concrete function type after the binding name: `let copy: (int) -> int throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// Generic-parameter arity and ordinary function arity are independent. The +// specialization has two types while the concrete function type has 3 inputs. +test "multiple generic parameters produce an arity-correct diagnostic" { + root.compiler.AssertRejected( + { + "case.baml": ` + function project(first: A, second: B, fallback: B) -> B { second } + function demo() -> bool { + let project_value = project; + true + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `project` needs concrete type arguments before it can be stored in `project_value`. Specialize it explicitly, for example `project`. Or write the concrete function type after the binding name: `let project_value: (int, int, int) -> int throws never = project`. Calling `project(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// A made-up `` example can violate a declared bound. In that case the +// compiler describes the parameters and signature shape without pretending a +// particular specialization is valid. +test "bounded generic diagnostic does not invent invalid type arguments" { + root.compiler.AssertRejected( + { + "case.baml": ` + function bounded(first: A, second: B) -> A { first } + function demo() -> bool { + let bounded_value = bounded; + true + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `bounded` needs concrete type arguments before it can be stored in `bounded_value`. Specialize it explicitly by choosing concrete types for A, B; each type must satisfy its declared bounds. Or write the concrete function type after the binding name, using `(A, B) -> A throws never` as the shape and replacing A, B with the types you want: `let bounded_value: ... = bounded`. Calling `bounded(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// A function annotation cannot infer a parameter that never appears in the +// function type. Only explicit specialization can choose this phantom T. +test "phantom generic explains why callback context is insufficient" { + root.compiler.AssertRejected( + { + "case.baml": ` + function phantom() -> string { "ok" } + function requires_never(cb: () -> string throws never) -> string { cb() } + function demo() -> string { requires_never(phantom) } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "cannot determine every type argument for generic function `phantom` from the expected function type. Specialize it explicitly, for example `phantom`. Calling `phantom(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// `reflect.AnyFunction` erases the concrete signature, so it cannot provide +// the type information needed to realize a generic function value. +test "erased reflection function type does not specialize a generic value" { + root.compiler.AssertRejected( + { + "case.baml": ` + function ident(x: T) -> T throws never { x } + function demo() -> bool { + let f: reflect.AnyFunction = ident; + true + } + `, + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "cannot determine every type argument for generic function `ident` from the expected function type. Specialize it explicitly, for example `ident`. Or write the concrete function type after the binding name: `let f: (int) -> int throws never = ident`. Calling `ident(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ) +} + +// Every expected diagnostic is matched against one actual diagnostic. This +// prevents message fragments from separate errors from jointly passing one +// assertion, and locks both diagnostic count and source order. +test "multiple bare bindings report distinct ordered diagnostics" { + root.compiler.AssertMultiRejected( + { + "case.baml": ` + function identity(value: T) -> T { value } + function demo() -> bool { + let first = identity; + let second = identity; + true + } + `, + }, + [ + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `identity` needs concrete type arguments before it can be stored in `first`. Specialize it explicitly, for example `identity`. Or write the concrete function type after the binding name: `let first: (int) -> int throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + root.compiler.ExactDiagnostic { + code: "E0001", + message: "generic function `identity` needs concrete type arguments before it can be stored in `second`. Specialize it explicitly, for example `identity`. Or write the concrete function type after the binding name: `let second: (int) -> int throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument", + }, + ], + ) +} diff --git a/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml b/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml index 724caa4dc1c..ca6044c4e62 100644 --- a/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml +++ b/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml @@ -430,7 +430,10 @@ test "selfless_member_through_the_type_carries_the_whole_frame" { assert.equal((Plate as Embosser).fresh(9).label(), "plate 9") let make = (Plate as Embosser).fresh assert.equal(make(4).label(), "plate 4") - // Own generics through a value reference, solved by the later call. - let stamp = (Plate as Embosser).emboss + // A stored generic method value must realize its own generic parameter at + // the binding; unlike a direct call, a later call cannot specialize it. + // Qualified interface projections cannot carry standalone `<...>`, so the + // concrete function annotation is the available spelling here. + let stamp: (int) -> string throws never = (Plate as Embosser).emboss assert.equal(stamp(7), "emboss int") } diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/bytecode.snap b/baml_language/crates/baml_tests/snapshots/baml_src/bytecode.snap index e2faf6dc97a..e7998b5c31d 100644 --- a/baml_language/crates/baml_tests/snapshots/baml_src/bytecode.snap +++ b/baml_language/crates/baml_tests/snapshots/baml_src/bytecode.snap @@ -622,6 +622,15 @@ function $init_test(registry: unknown) -> null { load_var ?1 call ? pop 1 + load_var ?1 + call ? + pop 1 + load_var ?1 + call ? + pop 1 + load_var ?1 + call ? + pop 1 load_const ? return } diff --git a/baml_language/crates/baml_tests/snapshots/baml_src/ns_compiler/ns_generics/ns_function_values/bytecode.snap b/baml_language/crates/baml_tests/snapshots/baml_src/ns_compiler/ns_generics/ns_function_values/bytecode.snap new file mode 100644 index 00000000000..2964ff892ff --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/baml_src/ns_compiler/ns_generics/ns_function_values/bytecode.snap @@ -0,0 +1,157 @@ +--- +source: crates/baml_tests/src/corpus.rs +--- +function compiler.generics.function_values.$init_test_ns_compiler_ns_generics_ns_function_values_callback_effects_and_methods(registry: testing.TestCollector) -> null { + load_var registry + load_const "root.compiler.generics.function_values" + load_const "invoked generic callback effect propagates through wrapper" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "invoked callback effect is rejected by a closed caller" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "unused and caught callback effects do not escape" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "explicit throws never callback rejects a throwing generic function" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "generic method values follow the same realization rule" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "generic method value can specialize from callback context" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "qualified method value recommends its available annotation spelling" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "qualified method value compiles with a concrete annotation" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_const null + return +} + +function compiler.generics.function_values.$init_test_ns_compiler_ns_generics_ns_function_values_contextual_specialization(registry: testing.TestCollector) -> null { + load_var registry + load_const "root.compiler.generics.function_values" + load_const "explicit specialization and concrete annotation compile" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "direct calls may infer a fresh type on every call" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "concrete value contexts specialize a generic function" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "a later sibling call argument may provide context" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "a later use cannot retroactively specialize a stored value" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "ambiguous callback union does not guess a specialization" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_const null + return +} + +function compiler.generics.function_values.$init_test_ns_compiler_ns_generics_ns_function_values_diagnostics(registry: testing.TestCollector) -> null { + load_var registry + load_const "root.compiler.generics.function_values" + load_const "bare generic function binding reports both concrete fixes" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "multiple generic parameters produce an arity-correct diagnostic" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "bounded generic diagnostic does not invent invalid type arguments" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "phantom generic explains why callback context is insufficient" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "erased reflection function type does not specialize a generic value" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_var registry + load_const "root.compiler.generics.function_values" + load_const "multiple bare bindings report distinct ordered diagnostics" + make_closure ., 0 + load_const null + call testing.TestCollector.register_test_at + pop 1 + load_const null + return +} diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/optional_parameter_defaults/baml_tests__diagnostic_errors__optional_parameter_defaults__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/optional_parameter_defaults/baml_tests__diagnostic_errors__optional_parameter_defaults__05_diagnostics.snap index 1bc4c18c018..e05301d544b 100644 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/optional_parameter_defaults/baml_tests__diagnostic_errors__optional_parameter_defaults__05_diagnostics.snap +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/optional_parameter_defaults/baml_tests__diagnostic_errors__optional_parameter_defaults__05_diagnostics.snap @@ -1,6 +1,5 @@ --- source: crates/baml_tests/src/generated_tests.rs -assertion_line: 40002 --- === COMPILER2 DIAGNOSTICS === [type] E0005 @@ -92,12 +91,3 @@ assertion_line: 40002 18 │ f(2) · ─ ╰──── - - [type] E0001 - - × mismatched types - ╭─[unsupported_contexts.baml:26:23] - 26 │ let f: NamedLimit = Filtered - · ────┬─── - · ╰── expected `NamedLimit`, found `(query: string, filter?: string | null) -> int throws never` - ╰──── diff --git a/baml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rs b/baml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rs index 5f366a3f38e..0d2be8b99fb 100644 --- a/baml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rs +++ b/baml_language/crates/baml_tests/src/compiler2_tir/explicit_type_args.rs @@ -287,38 +287,6 @@ function caller() -> string { "#); } -/// A bare reference to a generic function (`let f = identity`, no type args and -/// no expected type) is an *unrealized* function value, so it is rejected: a -/// generic function is a type constructor and must be specialized (`identity`) -/// or inferable from context before it can be used as a value. -#[test] -fn bare_generic_function_ref_rejected() { - let mut db = make_db(); - let file = db.file( - "test.baml", - r#" -function identity(x: T) -> T { x } -function caller() -> string { - let f = identity; - f("string") -} -"#, - ); - insta::assert_snapshot!(render_tir(&db, file), @r#" - function user.identity(x: T) -> T throws never { - { : T - x : T - } - } - function user.caller() -> string throws never { - { : string - let f = identity : (x: string) -> string throws never - f("string") : string - } - } - "#); -} - /// Multiple bound type args as a *value*: `pair` specializes BOTH /// params, yielding the concrete `(a: int, b: string) -> string`. #[test] @@ -516,6 +484,7 @@ function uses() -> int { let g = identity : (x: int) -> int throws never g(5) : int } + !! 141..149: generic function `identity` needs concrete type arguments before it can be stored in `g`. Specialize it explicitly, for example `identity`. Or write the concrete function type after the binding name: `let g: (int) -> int throws never = identity`. Calling `identity(...)` directly works only when that call's arguments or expected result determine every type argument } "); } diff --git a/baml_language/crates/baml_tests/tests/reflect_call_any.rs b/baml_language/crates/baml_tests/tests/reflect_call_any.rs index 69b3eb99d2e..7c10a7748c0 100644 --- a/baml_language/crates/baml_tests/tests/reflect_call_any.rs +++ b/baml_language/crates/baml_tests/tests/reflect_call_any.rs @@ -813,34 +813,6 @@ async fn instantiated_generic_function_reflects_precisely_and_dispatches() { ); } -#[tokio::test] -async fn call_any_reports_unspecialized_generic_function() { - let output = baml_test!( - r#" - function ident(x: T) -> T throws never { - return x - } - - function main() -> string throws unknown { - let f: reflect.AnyFunction = ident - let _ = reflect.call_any(f, { "x": 42 }) catch (e) { - reflect.errors.CompilationError => { - return e.diagnostics[0].code + "|" + e.diagnostics[0].message - }, - _ => throw e, - } - return "generic call unexpectedly succeeded" - } - "# - ); - assert_eq!( - output.result, - Ok(BexExternalValue::String( - "E0165|generic function `ident` cannot be extracted through reflection: its signature still mentions its own type parameters".into() - )) - ); -} - #[tokio::test] async fn pinned_call_any_declares_unspecialized_generic_compilation_error() { let output = baml_test!( @@ -869,33 +841,6 @@ async fn pinned_call_any_declares_unspecialized_generic_compilation_error() { ); } -/// An *uninstantiated* generic reference — no turbofish and nothing to infer -/// from — is a compile error: a callable value must carry a realized frame, so -/// there is no such thing as a half-generic one to reflect on. Ignored until -/// that diagnostic exists; today the reference is accepted and only fails later, -/// at `reflect.signature`, with a misleading "expects a function value". -#[ignore = "pending the uninstantiated-generic-reference diagnostic"] -#[tokio::test] -async fn uninstantiated_generic_function_reference_is_a_compile_error() { - let output = baml_test!( - r#" - function ident(x: T) -> T throws never { - return x - } - - function main() -> string throws never { - let f: reflect.AnyFunction = ident - return "unreachable" - } - "# - ); - assert!( - output.result.is_err(), - "expected a compile error naming the uninferable type parameter, got {:?}", - output.result - ); -} - #[tokio::test] async fn signature_object_literal_construction() { let output = baml_test!(