Skip to content

Commit

Permalink
Class generics pass down to constructor (#200)
Browse files Browse the repository at this point in the history
- Also adds type check support for it
- Emoji fix in CLI
- Add Exportable::Enum
- Add temp enum implementation
- Add fix to reporting
- Class hoisting fix
  • Loading branch information
kaleidawave authored Aug 29, 2024
1 parent e69e2c5 commit 5369041
Show file tree
Hide file tree
Showing 19 changed files with 380 additions and 149 deletions.
21 changes: 11 additions & 10 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,25 @@ jobs:

- name: Run checker on example files
shell: bash
continue-on-error: true
run: |
files=(
"https://jsr.io/@yossydev/hello-world/1.0.0/index.ts"
"https://jsr.io/@bengineering/shuffle-binary/0.0.1/index.ts"
"https://jsr.io/@bengineering/mulberry32/0.0.1/mod.ts"
"https://jsr.io/@luca/cases/1.0.0/mod.ts"
"https://jsr.io/@std/assert/1.0.2/assertion_error.ts"
"https://jsr.io/@std/text/1.0.3/levenshtein_distance.ts"
"https://jsr.io/@gnome/monads/0.0.0/src/option.ts"
"https://raw.githubusercontent.com/getify/deePool/master/src/deePool.js"
"https://raw.githubusercontent.com/silen-z/fiveway/main/packages/fiveway/src/id.ts"
https://jsr.io/@yossydev/hello-world/1.0.0/index.ts
https://jsr.io/@bengineering/shuffle-binary/0.0.1/index.ts
https://jsr.io/@bengineering/mulberry32/0.0.1/mod.ts
https://jsr.io/@luca/cases/1.0.0/mod.ts
https://jsr.io/@std/assert/1.0.2/assertion_error.ts
https://jsr.io/@std/text/1.0.3/levenshtein_distance.ts
https://jsr.io/@gnome/monads/0.0.0/src/option.ts
https://raw.githubusercontent.com/getify/deePool/master/src/deePool.js
https://raw.githubusercontent.com/silen-z/fiveway/main/packages/fiveway/src/id.ts
)
for url in "${files[@]}"; do
header="--- $url ---"
echo $header
curl -s $url > temp.ts
./target/release/ezno check temp.ts --timings
./target/release/ezno check temp.ts --timings || true
echo "${header//?/-}"
echo ""
done
18 changes: 17 additions & 1 deletion checker/specification/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -3263,6 +3263,22 @@ doThingWithX(new Y())

- Argument of type [Y] { a: 2 } is not assignable to parameter of type X

#### Generics to constructor

```ts
class Box<T> {
value: T;

constructor(value: T) {
this.value = value;
}
}

const myBox = new Box<number>("hi");
```

- Argument of type "hi" is not assignable to parameter of type number

### Types

#### Non existent type
Expand Down Expand Up @@ -3696,7 +3712,7 @@ type GetPrefix<S, End> = S extends `${infer T} ${End}` ? T : false;

- Expected "Hello", found 4

#### `infer ... extends ...`
#### Infer with extends clause

```ts
type X<T> = T extends { a: infer I extends string } ? I : string;
Expand Down
17 changes: 13 additions & 4 deletions checker/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,8 +932,8 @@ pub enum TypeCheckWarning {
},
IgnoringAsExpression(SpanWithSource),
Unimplemented {
thing: &'static str,
at: SpanWithSource,
item: &'static str,
position: SpanWithSource,
},
UselessExpression {
expression_span: SpanWithSource,
Expand All @@ -960,6 +960,10 @@ pub enum TypeCheckWarning {
rhs: TypeStringRepresentation,
position: SpanWithSource,
},
ItemMustBeUsedWithFlag {
item: &'static str,
position: SpanWithSource,
},
}

impl From<TypeCheckWarning> for Diagnostic {
Expand Down Expand Up @@ -993,9 +997,14 @@ impl From<TypeCheckWarning> for Diagnostic {
position,
kind,
},
TypeCheckWarning::Unimplemented { thing, at } => {
Diagnostic::Position { reason: format!("Unsupported: {thing}"), position: at, kind }
TypeCheckWarning::Unimplemented { item, position } => {
Diagnostic::Position { reason: format!("Unsupported: {item}"), position, kind }
}
TypeCheckWarning::ItemMustBeUsedWithFlag { item, position } => Diagnostic::Position {
reason: format!("{item} must be used with 'extras' option"),
position,
kind,
},
TypeCheckWarning::UselessExpression { expression_span } => Diagnostic::Position {
reason: "Expression is always true".to_owned(),
position: expression_span,
Expand Down
31 changes: 30 additions & 1 deletion checker/src/features/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,12 @@ where
let mut function_environment = base_environment.new_lexical_environment(Scope::Function(scope));

if function.has_body() {
let type_parameters = function.type_parameters(&mut function_environment, checking_data);
let type_parameters = if let Some((ref prototype, _)) = constructor {
// Class generics here
class_generics_to_function_generics(*prototype, &checking_data.types)
} else {
function.type_parameters(&mut function_environment, checking_data)
};

// TODO should be in function, but then requires mutable environment :(
let this_constraint =
Expand Down Expand Up @@ -1131,3 +1136,27 @@ pub fn extract_name(expecting: TypeId, types: &TypeStore, environment: &Environm
TypeId::EMPTY_STRING
}
}

pub fn class_generics_to_function_generics(
prototype: TypeId,
types: &TypeStore,
) -> Option<GenericTypeParameters> {
types.get_type_by_id(prototype).get_parameters().map(|parameters| {
parameters
.into_iter()
.map(|ty| {
let Type::RootPolyType(PolyNature::StructureGeneric { name, .. }) =
types.get_type_by_id(ty)
else {
unreachable!()
};
crate::types::generics::GenericTypeParameter {
name: name.clone(),
// Using its associated [`Type`], its restriction can be found
type_id: ty,
default: None,
}
})
.collect()
})
}
4 changes: 2 additions & 2 deletions checker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,10 @@ where
}

/// TODO temp, needs better place
pub fn raise_unimplemented_error(&mut self, item: &'static str, span: SpanWithSource) {
pub fn raise_unimplemented_error(&mut self, item: &'static str, position: SpanWithSource) {
if self.unimplemented_items.insert(item) {
self.diagnostics_container
.add_warning(TypeCheckWarning::Unimplemented { thing: item, at: span });
.add_warning(TypeCheckWarning::Unimplemented { item, position });
}
}

Expand Down
4 changes: 2 additions & 2 deletions checker/src/synthesis/block.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use parser::{ASTNode, Statement, StatementOrDeclaration};
use parser::{ASTNode, Declaration, Statement, StatementOrDeclaration};

use crate::{context::Environment, diagnostics::TypeCheckWarning, CheckingData};

Expand Down Expand Up @@ -43,7 +43,7 @@ pub(super) fn synthesise_block<T: crate::ReadFromFS>(
e,
StatementOrDeclaration::Statement(
Statement::Comment(..) | Statement::MultiLineComment(..) | Statement::Empty(..)
)
) | StatementOrDeclaration::Declaration(Declaration::Function(..))
)
}) {
checking_data.diagnostics_container.add_warning(TypeCheckWarning::Unreachable(
Expand Down
87 changes: 75 additions & 12 deletions checker/src/synthesis/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use crate::{
context::{Environment, InformationChain, LocalInformation},
diagnostics::TypeCheckError,
features::functions::{
function_to_property, synthesise_function, ClassPropertiesToRegister,
FunctionRegisterBehavior, GetterSetter, SynthesisableFunction,
class_generics_to_function_generics, function_to_property, synthesise_function,
ClassPropertiesToRegister, FunctionRegisterBehavior, GetterSetter, ReturnType,
SynthesisableFunction,
},
types::{
classes::ClassValue,
properties::{PropertyKey, Publicity},
FunctionType, PolyNature,
FunctionType, PolyNature, SynthesisedParameters,
},
CheckingData, FunctionId, PropertyValue, Scope, Type, TypeId,
};
Expand Down Expand Up @@ -492,12 +493,13 @@ fn synthesise_class_declaration_extends_and_members<
/// Also sets variable for hoisting
///
/// Builds the type of the class
#[must_use]
pub(super) fn register_statement_class_with_members<T: crate::ReadFromFS>(
class_type: TypeId,
class: &ClassDeclaration<StatementPosition>,
environment: &mut Environment,
checking_data: &mut CheckingData<T, super::EznoParser>,
) {
) -> TypeId {
let class_type2 = checking_data.types.get_type_by_id(class_type);

let Type::Class { name: _, type_parameters } = class_type2 else {
Expand All @@ -516,15 +518,18 @@ pub(super) fn register_statement_class_with_members<T: crate::ReadFromFS>(

sub_environment.named_types.insert(name.clone(), *parameter);
}
register_extends_and_member(class, class_type, &mut sub_environment, checking_data);

let result =
register_extends_and_member(class, class_type, &mut sub_environment, checking_data);
{
let crate::context::LocalInformation { current_properties, prototypes, .. } =
sub_environment.info;
environment.info.current_properties.extend(current_properties);
environment.info.prototypes.extend(prototypes);
}
result
} else {
register_extends_and_member(class, class_type, environment, checking_data);
register_extends_and_member(class, class_type, environment, checking_data)
}
}

Expand All @@ -533,7 +538,7 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
class_type: TypeId,
environment: &mut Environment,
checking_data: &mut CheckingData<T, super::EznoParser>,
) {
) -> TypeId {
if let Some(ref extends) = class.extends {
let extends = get_extends_as_simple_type(extends, environment, checking_data);

Expand All @@ -547,6 +552,9 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
// checking_data.local_type_mappings.types_to_types.push(class.position, class_type);

let mut members_iter = class.members.iter().peekable();

let mut found_constructor = None::<TypeId>;

while let Some(member) = members_iter.next() {
match &member.on {
ClassMember::Method(initial_is_static, method) => {
Expand Down Expand Up @@ -591,7 +599,7 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
.with_source(environment.get_source()),
},
);
return;
continue;
}
} else {
let actual = synthesise_shape(method, environment, checking_data);
Expand Down Expand Up @@ -716,14 +724,69 @@ fn register_extends_and_member<T: crate::ReadFromFS>(
value,
);
}
ClassMember::Constructor(c) => {
if !c.has_body() {
crate::utilities::notify!("TODO possible constructor overloading");
}
ClassMember::Constructor(constructor) => {
let internal_effect = get_internal_function_effect_from_decorators(
&member.decorators,
"",
environment,
);

let mut actual = synthesise_shape(constructor, environment, checking_data);
actual.0 = class_generics_to_function_generics(class_type, &checking_data.types);

let constructor = build_overloaded_function(
FunctionId(environment.get_source(), constructor.position.start),
crate::types::functions::FunctionBehavior::Constructor {
// The prototype of the base object
prototype: class_type,
// The id of the generic that needs to be pulled out
this_object_type: TypeId::ERROR_TYPE,
name: TypeId::ANY_TYPE,
},
Vec::new(),
actual,
environment,
&mut checking_data.types,
&mut checking_data.diagnostics_container,
if let Some(ie) = internal_effect {
ie.into()
} else {
crate::types::functions::FunctionEffect::Unknown
},
);

found_constructor = Some(constructor);
}
ClassMember::StaticBlock(_) | ClassMember::Comment(_, _, _) => {}
}
}

if let Some(constructor) = found_constructor {
constructor
} else {
let return_type =
ReturnType(class_type, class.position.with_source(environment.get_source()));
build_overloaded_function(
FunctionId(environment.get_source(), class.position.start),
crate::types::functions::FunctionBehavior::Constructor {
// The prototype of the base object
prototype: class_type,
// The id of the generic that needs to be pulled out
this_object_type: TypeId::ERROR_TYPE,
name: TypeId::ANY_TYPE,
},
Vec::new(),
crate::features::functions::PartialFunction(
class_generics_to_function_generics(class_type, &checking_data.types),
SynthesisedParameters::default(),
Some(return_type),
),
environment,
&mut checking_data.types,
&mut checking_data.diagnostics_container,
crate::types::functions::FunctionEffect::Unknown,
)
}
}

/// For hoisting using the class as a type annotation
Expand Down
Loading

0 comments on commit 5369041

Please sign in to comment.