Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for excess generic arguments #160

Merged
merged 8 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions checker/specification/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -2749,6 +2749,26 @@ unwrap(16) satisfies 16;

- Expected string, found 5

#### Excess generic arguments

```ts
declare function generic<T>(a: T);

generic<string, number>("something");
```

- Expected 1 type argument, but got 2

#### Passing generic type to non-generic function

```ts
declare function func();

func<string>();
```

- Cannot pass a type argument to a non-generic function

#### Across alias

```ts
Expand Down
21 changes: 18 additions & 3 deletions checker/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}"),
Expand All @@ -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}"),
Expand All @@ -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,
Expand Down Expand Up @@ -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,
}
Expand Down
67 changes: 56 additions & 11 deletions checker/src/types/calling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1573,17 +1579,56 @@ fn synthesise_argument_expressions_wrt_parameters<T: ReadFromFS, A: crate::ASTIm
let function = checking_data.types.get_function_from_id(function.function);

let type_arguments_restrictions =
if let (Some(ref type_parameters), Some(call_site_type_arguments)) =
(&function.type_parameters, call_site_type_arguments)
{
Some(synthesise_call_site_type_argument_hints(
type_parameters,
call_site_type_arguments,
&checking_data.types,
environment,
))
} else {
None
match (&function.type_parameters, call_site_type_arguments) {
(None, Some(call_site_type_arguments)) => {
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();
Expand Down
1 change: 1 addition & 0 deletions checker/src/types/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ pub(crate) fn set_property<E: CallCheckingBehavior>(
| FunctionCallingError::NoLogicForIdentifier(..)
| FunctionCallingError::NotCallable { .. }
| FunctionCallingError::ExcessArguments { .. }
| FunctionCallingError::ExcessTypeArguments { .. }
| FunctionCallingError::MissingArgument { .. } => unreachable!(),
FunctionCallingError::ReferenceRestrictionDoesNotMatch { .. } => {
todo!()
Expand Down
2 changes: 2 additions & 0 deletions src/utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ pub(crate) fn upgrade_self() -> Result<String, Box<dyn std::error::Error>> {
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;
Expand Down