diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 9ec06bf8..7e1b66f1 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -2749,6 +2749,26 @@ unwrap(16) satisfies 16; - Expected string, found 5 +#### Excess generic arguments + +```ts +declare function generic(a: T); + +generic("something"); +``` + +- Expected 1 type argument, but got 2 + +#### Passing generic type to non-generic function + +```ts +declare function func(); + +func(); +``` + +- Cannot pass a type argument to a non-generic function + #### Across alias ```ts diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index f6de7ccf..ec90a0b0 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -879,6 +879,7 @@ pub struct CannotRedeclareVariable<'a> { pub name: &'a str, } +/// `context` is the what kind of a function call the error happened in. (for example tagged template literals or JSX) fn function_calling_error_diagnostic( error: FunctionCallingError, kind: crate::DiagnosticKind, @@ -894,7 +895,7 @@ fn function_calling_error_diagnostic( } => { if let Some((restriction_pos, restriction)) = restriction { Diagnostic::PositionWithAdditionalLabels { - reason: format!( "Argument of type {argument_type} is not assignable to parameter of type {restriction}{context}" ), + reason: format!("Argument of type {argument_type} is not assignable to parameter of type {restriction}{context}"), position: argument_position, labels: vec![( format!("{parameter_type} was specialised with type {restriction}"), @@ -904,7 +905,7 @@ fn function_calling_error_diagnostic( } } else { Diagnostic::PositionWithAdditionalLabels { - reason: format!( "Argument of type {argument_type} is not assignable to parameter of type {parameter_type}{context}", ), + reason: format!( "Argument of type {argument_type} is not assignable to parameter of type {parameter_type}{context}"), position: argument_position, labels: vec![( format!("Parameter has type {parameter_type}"), @@ -928,6 +929,20 @@ fn function_calling_error_diagnostic( FunctionCallingError::ExcessArguments { count: _, position } => { Diagnostic::Position { reason: format!("Excess argument{context}"), position, kind } } + FunctionCallingError::ExcessTypeArguments { expected_count, count, position } => { + let reason = if expected_count == 0 { + format!("Cannot pass a type argument to a non-generic function{context}") + } else if expected_count == 1 { + format!("Expected 1 type argument, but got {count}{context}") + } else { + format!("Expected {expected_count} type arguments, but got {count}{context}") + }; + Diagnostic::Position { + position, + kind, + reason + } + } FunctionCallingError::NotCallable { calling, call_site } => Diagnostic::Position { reason: format!("Cannot call type {calling}{context}"), position: call_site, @@ -990,7 +1005,7 @@ fn function_calling_error_diagnostic( }, FunctionCallingError::MismatchedThis { call_site, expected, found } => { Diagnostic::Position { - reason: format!( "The 'this' context of the function is expected to be {expected}, found {found}{context}" ), + reason: format!("The 'this' context of the function is expected to be {expected}, found {found}{context}"), position: call_site, kind, } diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 13d9c934..fda0606c 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -647,6 +647,12 @@ pub enum FunctionCallingError { count: usize, position: SpanWithSource, }, + + ExcessTypeArguments { + expected_count: usize, + count: usize, + position: SpanWithSource, + }, NotCallable { calling: TypeStringRepresentation, call_site: SpanWithSource, @@ -1573,17 +1579,56 @@ fn synthesise_argument_expressions_wrt_parameters { + let first_excess_type_parameter = &call_site_type_arguments[0]; + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionCallingError( + FunctionCallingError::ExcessTypeArguments { + expected_count: 0, + count: call_site_type_arguments.len(), + position: first_excess_type_parameter.1, + }, + ), + ); + + None + } + (Some(ref function_type_parameters), Some(call_site_type_arguments)) => { + let expected_parameters_length = function_type_parameters.0.len(); + let provided_parameters_length = call_site_type_arguments.len(); + if provided_parameters_length > expected_parameters_length { + let first_excess_type_parameter = + &call_site_type_arguments[expected_parameters_length]; + let last_excess_type_parameter = call_site_type_arguments + .last() + .unwrap_or(first_excess_type_parameter); + + let error_position = first_excess_type_parameter + .1 + .without_source() + .union(last_excess_type_parameter.1.without_source()) + .with_source(first_excess_type_parameter.1.source); + + checking_data.diagnostics_container.add_error( + TypeCheckError::FunctionCallingError( + FunctionCallingError::ExcessTypeArguments { + expected_count: expected_parameters_length, + count: provided_parameters_length, + position: error_position, + }, + ), + ); + } + Some(synthesise_call_site_type_argument_hints( + function_type_parameters, + call_site_type_arguments, + &checking_data.types, + environment, + )) + } + + _ => None, }; let parameters = function.parameters.clone(); diff --git a/checker/src/types/properties.rs b/checker/src/types/properties.rs index fc6d0783..b2aafcfc 100644 --- a/checker/src/types/properties.rs +++ b/checker/src/types/properties.rs @@ -873,6 +873,7 @@ pub(crate) fn set_property( | FunctionCallingError::NoLogicForIdentifier(..) | FunctionCallingError::NotCallable { .. } | FunctionCallingError::ExcessArguments { .. } + | FunctionCallingError::ExcessTypeArguments { .. } | FunctionCallingError::MissingArgument { .. } => unreachable!(), FunctionCallingError::ReferenceRestrictionDoesNotMatch { .. } => { todo!() diff --git a/src/utilities.rs b/src/utilities.rs index d17f2870..d90def09 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -136,6 +136,8 @@ pub(crate) fn upgrade_self() -> Result> { const EXPECTED_END: &str = "windows.exe"; #[cfg(target_os = "linux")] const EXPECTED_END: &str = "linux"; + #[cfg(target_os = "macos")] + const EXPECTED_END: &str = "macos"; let mut required_binary = None; let mut version_name = None;