From 9840f377f740fb0efa902ccdd4b9f386a50a1d2b Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Fri, 30 Jun 2023 23:18:58 +0300 Subject: [PATCH] chore(compiler): type references as expressions and phase tracking (#3177) No functional changes, preparatory pull request as part of the expression lifting escapade. Type references are expressions that reference type names. It's valuable to model them as expressions because they are needed at runtime. Once they are modeled as expressions, we can treat all expressions equally when it comes to lifting (which is the motivation for this refactor). We've changed the following places to use type references instead of a `UserDefinedType`. In all of these cases the types have a runtime presence. The rest of the places are just type annotations that are erased at runtime: - The class being instantiated in `new` expressions - Base classes - The type component in a `Reference::TypeMember` (static access). Furthermore, to support expression lifting, the type checker now records the phase of each expression during type checking and makes it available through `get_expr_phase`. Additionally, changed the left-hand-side of an assignment statement to be an `Expr` and not a `Reference`. Also in order to be able to lift it. Turn the variable component of `StmtKind::Assignment` from a simple `Symbol` to an `Expr` in order for it to also hold type and phase information. The JSII importer accidentally imported structs as `preflight` while they should be phase-independent. Misc: strip excess newlines when adding a line to `CodeMaker`. ## Checklist - [x] Title matches [Winglang's style guide](https://docs.winglang.io/contributing/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [x] Docs updated (only required for features) - [x] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Monada Contribution License](https://docs.winglang.io/terms-and-policies/contribution-license.html)*. --- examples/tests/invalid/class.w | 4 +- examples/tests/invalid/enums.w | 4 +- examples/tests/invalid/inflight_reassign.w | 10 + examples/tests/valid/custom_obj_id.w | 7 + libs/wingc/src/ast.rs | 50 +- libs/wingc/src/closure_transform.rs | 51 +- libs/wingc/src/docs.rs | 4 +- libs/wingc/src/fold.rs | 11 +- libs/wingc/src/jsify.rs | 124 ++-- libs/wingc/src/jsify/codemaker.rs | 7 +- libs/wingc/src/lsp/completions.rs | 88 +-- libs/wingc/src/lsp/hover.rs | 75 ++- libs/wingc/src/lsp/signature.rs | 13 +- .../hovers/builtin_instance_method.snap | 2 +- .../lsp/snapshots/hovers/class_property.snap | 2 +- libs/wingc/src/parser.rs | 93 ++- libs/wingc/src/type_check.rs | 541 +++++++++++------- .../wingc/src/type_check/class_fields_init.rs | 23 +- libs/wingc/src/type_check/jsii_importer.rs | 8 +- libs/wingc/src/visit.rs | 15 +- tools/hangar/__snapshots__/invalid.ts.snap | 104 +++- .../valid/custom_obj_id.w_compile_tf-aws.md | 111 ++++ .../valid/custom_obj_id.w_test_sim.md | 12 + 23 files changed, 910 insertions(+), 449 deletions(-) create mode 100644 examples/tests/invalid/inflight_reassign.w create mode 100644 examples/tests/valid/custom_obj_id.w create mode 100644 tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_compile_tf-aws.md create mode 100644 tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_test_sim.md diff --git a/examples/tests/invalid/class.w b/examples/tests/invalid/class.w index 11f57028bae..297f2039077 100644 --- a/examples/tests/invalid/class.w +++ b/examples/tests/invalid/class.w @@ -72,11 +72,11 @@ class C7 extends x { struct S1 {} class C8 extends S1 { - //^^ Preflight class C8's parent is not a class + //^^ Expected "S1" to be a class } class C11 extends C11 { - //^^^ Class cannot extend itself + //^^^ Unknown symbol "C11" } class Student { diff --git a/examples/tests/invalid/enums.w b/examples/tests/invalid/enums.w index f78f3aa9f35..d7ea9571c19 100644 --- a/examples/tests/invalid/enums.w +++ b/examples/tests/invalid/enums.w @@ -3,7 +3,7 @@ enum SomeEnum { } let four = SomeEnum.FOUR; -// ERR ^^^^ enum value does not exist +// ERR ^^^^ Enum "SomeEnum" does not contain value "FOUR" let two = SomeEnum.TWO.TWO; -// ERR ^^^ +// ERR ^^^ Property not found diff --git a/examples/tests/invalid/inflight_reassign.w b/examples/tests/invalid/inflight_reassign.w new file mode 100644 index 00000000000..a38e40a40d8 --- /dev/null +++ b/examples/tests/invalid/inflight_reassign.w @@ -0,0 +1,10 @@ +let var xvar = "hello"; +let ylet = 123; + +inflight () => { + xvar = "hi"; +//^^^^ Variable cannot be reassigned from inflight + + ylet = 456; +//^^^^ Variable is not reassignable +}; \ No newline at end of file diff --git a/examples/tests/valid/custom_obj_id.w b/examples/tests/valid/custom_obj_id.w new file mode 100644 index 00000000000..0e54afe2229 --- /dev/null +++ b/examples/tests/valid/custom_obj_id.w @@ -0,0 +1,7 @@ +class Foo { } + +let foo1 = new Foo(); +let bar2 = new Foo() as "bar2"; + +assert(foo1.node.id == "Foo"); +assert(bar2.node.id == "bar2"); diff --git a/libs/wingc/src/ast.rs b/libs/wingc/src/ast.rs index 3cb6fce9aea..2334fb3092c 100644 --- a/libs/wingc/src/ast.rs +++ b/libs/wingc/src/ast.rs @@ -164,6 +164,13 @@ impl UserDefinedType { pub fn full_path_str(&self) -> String { self.full_path().iter().join(".") } + + pub fn to_expression(&self) -> Expr { + Expr::new( + ExprKind::Reference(Reference::TypeReference(self.clone())), + self.span.clone(), + ) + } } impl Display for UserDefinedType { @@ -319,11 +326,30 @@ pub struct Class { pub methods: Vec<(Symbol, FunctionDefinition)>, pub initializer: FunctionDefinition, pub inflight_initializer: FunctionDefinition, - pub parent: Option, + pub parent: Option, // the expression must be a reference to a user defined type pub implements: Vec, pub phase: Phase, } +impl Class { + /// Returns the `UserDefinedType` of the parent class, if any. + pub fn parent_udt(&self) -> Option { + let Some(expr) = &self.parent else { + return None; + }; + + let ExprKind::Reference(ref r) = expr.kind else { + return None; + }; + + let Reference::TypeReference(t) = r else { + return None; + }; + + Some(t.clone()) + } +} + #[derive(Debug)] pub struct Interface { pub name: Symbol, @@ -372,7 +398,7 @@ pub enum StmtKind { Return(Option), Expression(Expr), Assignment { - variable: Reference, + variable: Expr, value: Expr, }, Scope(Scope), @@ -419,7 +445,7 @@ pub struct StructField { #[derive(Debug)] pub enum ExprKind { New { - class: TypeAnnotation, + class: Box, // expression must be a reference to a user defined type obj_id: Option>, obj_scope: Option>, arg_list: ArgList, @@ -592,8 +618,10 @@ pub enum Reference { property: Symbol, optional_accessor: bool, }, + /// A reference to a type (e.g. `std.Json` or `MyResource` or `aws.s3.Bucket`) + TypeReference(UserDefinedType), /// A reference to a member inside a type: `MyType.x` or `MyEnum.A` - TypeMember { type_: UserDefinedType, property: Symbol }, + TypeMember { typeobject: Box, property: Symbol }, } impl Display for Reference { @@ -611,13 +639,13 @@ impl Display for Reference { }; write!(f, "{}.{}", obj_str, property.name) } - Reference::TypeMember { type_, property } => { - write!( - f, - "{}.{}", - TypeAnnotationKind::UserDefined(type_.clone()), - property.name - ) + Reference::TypeReference(type_) => write!(f, "{}", type_), + Reference::TypeMember { typeobject, property } => { + let ExprKind::Reference(ref r) = typeobject.kind else { + return write!(f, ".{}", property.name); + }; + + write!(f, "{}.{}", r, property.name) } } } diff --git a/libs/wingc/src/closure_transform.rs b/libs/wingc/src/closure_transform.rs index e01ff30f388..42bed9baf5f 100644 --- a/libs/wingc/src/closure_transform.rs +++ b/libs/wingc/src/closure_transform.rs @@ -139,12 +139,14 @@ impl Fold for ClosureTransformer { span: WingSpan::default(), }; + let class_udt = UserDefinedType { + root: new_class_name.clone(), + fields: vec![], + span: WingSpan::default(), + }; + let class_type_annotation = TypeAnnotation { - kind: TypeAnnotationKind::UserDefined(UserDefinedType { - root: new_class_name.clone(), - fields: vec![], - span: WingSpan::default(), - }), + kind: TypeAnnotationKind::UserDefined(class_udt.clone()), span: WingSpan::default(), }; @@ -177,21 +179,24 @@ impl Fold for ClosureTransformer { let class_init_body = vec![Stmt { idx: 0, kind: StmtKind::Assignment { - variable: Reference::InstanceMember { - object: Box::new(Expr::new( - ExprKind::Reference(Reference::InstanceMember { - object: Box::new(Expr::new( - ExprKind::Reference(Reference::Identifier(Symbol::new("this", WingSpan::default()))), - WingSpan::default(), - )), - property: Symbol::new("display", WingSpan::default()), - optional_accessor: false, - }), - WingSpan::default(), - )), - property: Symbol::new("hidden", WingSpan::default()), - optional_accessor: false, - }, + variable: Expr::new( + ExprKind::Reference(Reference::InstanceMember { + object: Box::new(Expr::new( + ExprKind::Reference(Reference::InstanceMember { + object: Box::new(Expr::new( + ExprKind::Reference(Reference::Identifier(Symbol::new("this", WingSpan::default()))), + WingSpan::default(), + )), + property: Symbol::new("display", WingSpan::default()), + optional_accessor: false, + }), + WingSpan::default(), + )), + property: Symbol::new("hidden", WingSpan::default()), + optional_accessor: false, + }), + WingSpan::default(), + ), value: Expr::new(ExprKind::Literal(Literal::Boolean(true)), WingSpan::default()), }, span: WingSpan::default(), @@ -272,7 +277,7 @@ impl Fold for ClosureTransformer { // ``` let new_class_instance = Expr::new( ExprKind::New { - class: class_type_annotation, + class: Box::new(class_udt.to_expression()), arg_list: ArgList { named_args: IndexMap::new(), pos_args: vec![], @@ -333,7 +338,9 @@ impl<'a> Fold for RenameThisTransformer<'a> { Reference::Identifier(ident) } } - Reference::InstanceMember { .. } | Reference::TypeMember { .. } => fold::fold_reference(self, node), + Reference::InstanceMember { .. } | Reference::TypeMember { .. } | Reference::TypeReference(_) => { + fold::fold_reference(self, node) + } } } } diff --git a/libs/wingc/src/docs.rs b/libs/wingc/src/docs.rs index 3a979dff8cf..0fbc01e82fb 100644 --- a/libs/wingc/src/docs.rs +++ b/libs/wingc/src/docs.rs @@ -7,7 +7,7 @@ use crate::{ jsify::codemaker::CodeMaker, type_check::{ jsii_importer::is_construct_base, Class, FunctionSignature, Interface, Namespace, Struct, SymbolKind, Type, - TypeRef, VariableInfo, + TypeRef, VariableInfo, VariableKind, }, }; @@ -94,7 +94,7 @@ impl Documented for VariableInfo { fn render_docs(&self) -> String { let mut modifiers = vec![]; - if self.is_member && self.is_static { + if let VariableKind::StaticMember = self.kind { modifiers.push("static"); } diff --git a/libs/wingc/src/fold.rs b/libs/wingc/src/fold.rs index ea265838b74..af769e77837 100644 --- a/libs/wingc/src/fold.rs +++ b/libs/wingc/src/fold.rs @@ -141,7 +141,7 @@ where StmtKind::Return(value) => StmtKind::Return(value.map(|value| f.fold_expr(value))), StmtKind::Expression(expr) => StmtKind::Expression(f.fold_expr(expr)), StmtKind::Assignment { variable, value } => StmtKind::Assignment { - variable: f.fold_reference(variable), + variable: f.fold_expr(variable), value: f.fold_expr(value), }, StmtKind::Scope(scope) => StmtKind::Scope(f.fold_scope(scope)), @@ -193,7 +193,7 @@ where .map(|(name, def)| (f.fold_symbol(name), f.fold_function_definition(def))) .collect(), initializer: f.fold_function_definition(node.initializer), - parent: node.parent.map(|parent| f.fold_user_defined_type(parent)), + parent: node.parent.map(|parent| f.fold_expr(parent)), implements: node .implements .into_iter() @@ -257,7 +257,7 @@ where obj_scope, arg_list, } => ExprKind::New { - class: f.fold_type_annotation(class), + class: Box::new(f.fold_expr(*class)), obj_id, obj_scope: obj_scope.map(|scope| Box::new(f.fold_expr(*scope))), arg_list: f.fold_args(arg_list), @@ -364,8 +364,9 @@ where property: f.fold_symbol(property), optional_accessor, }, - Reference::TypeMember { type_, property } => Reference::TypeMember { - type_: f.fold_user_defined_type(type_), + Reference::TypeReference(udt) => Reference::TypeReference(f.fold_user_defined_type(udt)), + Reference::TypeMember { typeobject, property } => Reference::TypeMember { + typeobject: Box::new(f.fold_expr(*typeobject)), property: f.fold_symbol(property), }, } diff --git a/libs/wingc/src/jsify.rs b/libs/wingc/src/jsify.rs index 15985d40502..b02fda10671 100644 --- a/libs/wingc/src/jsify.rs +++ b/libs/wingc/src/jsify.rs @@ -17,17 +17,18 @@ use std::{ use crate::{ ast::{ ArgList, BinaryOperator, Class as AstClass, ClassField, Expr, ExprKind, FunctionBody, FunctionDefinition, - InterpolatedStringPart, Literal, Phase, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotation, - TypeAnnotationKind, UnaryOperator, + InterpolatedStringPart, Literal, Phase, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotationKind, + UnaryOperator, }, comp_ctx::{CompilationContext, CompilationPhase}, dbg_panic, debug, diagnostic::{report_diagnostic, Diagnostic, WingSpan}, files::Files, type_check::{ - resolve_user_defined_type, + resolve_udt_from_expr, resolve_user_defined_type, symbol_env::{LookupResult, SymbolEnv, SymbolEnvRef}, - ClassLike, SymbolKind, Type, TypeRef, Types, UnsafeRef, VariableInfo, CLASS_INFLIGHT_INIT_NAME, HANDLE_METHOD_NAME, + ClassLike, SymbolKind, Type, TypeRef, Types, UnsafeRef, VariableInfo, VariableKind, CLASS_INFLIGHT_INIT_NAME, + HANDLE_METHOD_NAME, }, visit::{self, Visit}, MACRO_REPLACE_ARGS, MACRO_REPLACE_ARGS_TEXT, MACRO_REPLACE_SELF, WINGSDK_ASSEMBLY_NAME, WINGSDK_RESOURCE, @@ -222,8 +223,10 @@ impl<'a> JSifier<'a> { property, optional_accessor: _, } => self.jsify_expression(object, ctx) + "." + &property.to_string(), - Reference::TypeMember { type_, property } => { - self.jsify_type(&TypeAnnotationKind::UserDefined(type_.clone()), ctx) + "." + &property.to_string() + Reference::TypeReference(udt) => self.jsify_type(&TypeAnnotationKind::UserDefined(udt.clone()), ctx), + Reference::TypeMember { typeobject, property } => { + let typename = self.jsify_expression(typeobject, ctx); + typename + "." + &property.to_string() } } } @@ -309,7 +312,7 @@ impl<'a> JSifier<'a> { // user-defined types), we simply instantiate the type directly (maybe in the future we will // allow customizations of user-defined types as well, but for now we don't). - let ctor = self.jsify_type(&class.kind, ctx); + let ctor = self.jsify_expression(class, ctx); let scope = if is_preflight_class && class_type.std_construct_args { if let Some(scope) = obj_scope { @@ -722,7 +725,7 @@ impl<'a> JSifier<'a> { StmtKind::Expression(e) => CodeMaker::one_line(format!("{};", self.jsify_expression(e, ctx))), StmtKind::Assignment { variable, value } => CodeMaker::one_line(format!( "{} = {};", - self.jsify_reference(&variable, ctx), + self.jsify_expression(variable, ctx), self.jsify_expression(value, ctx) )), StmtKind::Scope(scope) => { @@ -955,8 +958,9 @@ impl<'a> JSifier<'a> { let (mut captured_types, captured_vars) = self.scan_captures(class, &inflight_methods); if let Some(parent) = &class.parent { - let parent_type = resolve_user_defined_type(&parent, env, 0).unwrap(); - captured_types.insert(parent.full_path(), parent_type); + let parent_type_udt = resolve_udt_from_expr(&parent).unwrap(); + let parent_type = resolve_user_defined_type(&parent_type_udt, env, 0).unwrap(); + captured_types.insert(parent_type_udt.full_path(), parent_type); } // Get all references between inflight methods and preflight values @@ -988,7 +992,7 @@ impl<'a> JSifier<'a> { // default base class for preflight classes is `core.Resource` let extends = if let Some(parent) = &class.parent { - format!(" extends {}", jsify_type_name(&parent.full_path(), ctx.phase)) + format!(" extends {}", self.jsify_expression(&parent, ctx)) } else { format!(" extends {}", STDLIB_CORE_RESOURCE) }; @@ -1260,7 +1264,8 @@ impl<'a> JSifier<'a> { // Handle parent class: Need to call super and pass its captured fields (we assume the parent client is already written) let mut lifted_by_parent = vec![]; if let Some(parent) = &class.parent { - if let Ok(parent_type) = resolve_user_defined_type(parent, env, 0) { + let parent_udt = resolve_udt_from_expr(parent).unwrap(); + if let Ok(parent_type) = resolve_user_defined_type(&parent_udt, env, 0) { lifted_by_parent.extend(self.get_lifted_fields(parent_type)); } } @@ -1278,7 +1283,7 @@ impl<'a> JSifier<'a> { class_code.open(format!( "class {name}{} {{", if let Some(parent) = &class.parent { - format!(" extends {}", jsify_type_name(&parent.full_path(), ctx.phase)) + format!(" extends {}", self.jsify_expression(&parent, ctx)) } else { "".to_string() } @@ -1432,14 +1437,14 @@ impl<'a> JSifier<'a> { let refs = refs .iter() .filter(|(m, _)| { - (*m == CLASS_INFLIGHT_INIT_NAME - || !class_type - .as_class() - .unwrap() - .get_method(&m.as_str().into()) - .expect(&format!("method {m} doesn't exist in {class_name}")) - .is_static) - ^ (matches!(bind_method_kind, BindMethod::Type)) + let var_kind = class_type + .as_class() + .unwrap() + .get_method(&m.as_str().into()) + .expect(&format!("method {m} doesn't exist in {class_name}")) + .kind; + let is_static = matches!(var_kind, VariableKind::StaticMember); + (*m == CLASS_INFLIGHT_INIT_NAME || !is_static) ^ (matches!(bind_method_kind, BindMethod::Type)) }) .collect_vec(); @@ -1765,6 +1770,25 @@ impl<'a> FieldReferenceVisitor<'a> { kind: ComponentKind::Member(var), }]; } + Reference::TypeReference(type_) => { + let env = self.env.unwrap(); + + // Get the type we're accessing a member of + let Ok(t) = resolve_user_defined_type(type_, &env, self.statement_index) else { + return vec![]; + }; + + // If the type we're referencing isn't a preflight class then skip it + if t.as_preflight_class().is_none() { + return vec![]; + }; + + return vec![Component { + text: format!("{type_}"), + span: type_.span.clone(), + kind: ComponentKind::ClassType(t), + }]; + } Reference::InstanceMember { object, property, @@ -1784,13 +1808,17 @@ impl<'a> FieldReferenceVisitor<'a> { let obj = self.analyze_expr(&object); return [obj, prop].concat(); } - Reference::TypeMember { type_, property } => { - let env = self.env.unwrap(); + Reference::TypeMember { typeobject, property } => { + let obj = self.analyze_expr(&typeobject); - // Get the type we're accessing a member of - let Ok(t) = resolve_user_defined_type(type_, &env, self.statement_index) else { + let Some(first) = obj.first() else { + return vec![]; + }; + + let ComponentKind::ClassType(t) = first.kind else { return vec![]; }; + // If the type we're referencing isn't a preflight class then skip it let Some(class) = t.as_preflight_class() else { return vec![]; @@ -1805,18 +1833,13 @@ impl<'a> FieldReferenceVisitor<'a> { .as_variable() .expect("variable"); - return vec![ - Component { - text: format!("{type_}"), - span: type_.span.clone(), - kind: ComponentKind::ClassType(t), - }, - Component { - text: property.name.clone(), - span: property.span.clone(), - kind: ComponentKind::Member(var), - }, - ]; + let prop = vec![Component { + text: property.name.clone(), + span: property.span.clone(), + kind: ComponentKind::Member(var), + }]; + + [obj, prop].concat() } } } @@ -1945,23 +1968,6 @@ impl<'a> CaptureScanner<'a> { } impl<'ast> Visit<'ast> for CaptureScanner<'ast> { - fn visit_expr_new( - &mut self, - node: &'ast Expr, - class: &'ast TypeAnnotation, - obj_id: &'ast Option>, - obj_scope: &'ast Option>, - arg_list: &'ast ArgList, - ) { - // we want to only capture the type annotation in the case of "new X" because - // other cases of type annotation are actually erased in the javascript code. - if let TypeAnnotationKind::UserDefined(u) = &class.kind { - self.consider_reference(&u.full_path()); - } - - visit::visit_expr_new(self, node, class, obj_id, obj_scope, arg_list); - } - fn visit_reference(&mut self, node: &'ast Reference) { match node { Reference::Identifier(symb) => { @@ -1972,12 +1978,12 @@ impl<'ast> Visit<'ast> for CaptureScanner<'ast> { self.consider_reference(&vec![symb.clone()]); } - Reference::TypeMember { type_, .. } => { - self.consider_reference(&type_.full_path()); - } - // this is the case of "object.property". if we need to capture "object", it will be captured - // as an identifier, so we can skip it here. + Reference::TypeReference(t) => self.consider_reference(&t.full_path()), + + // this is the case of "object.property" (or `Type.property`). if we need to capture "object", + // it will be captured as an identifier, so we can skip it here. + Reference::TypeMember { .. } => {} Reference::InstanceMember { .. } => {} } diff --git a/libs/wingc/src/jsify/codemaker.rs b/libs/wingc/src/jsify/codemaker.rs index eef3d72a1b1..995ad46306c 100644 --- a/libs/wingc/src/jsify/codemaker.rs +++ b/libs/wingc/src/jsify/codemaker.rs @@ -23,8 +23,13 @@ impl CodeMaker { /// Emits a line of code with the current indent. pub fn line>(&mut self, line: S) { + let line: String = line.into(); + + // remove trailing newline + let line = line.strip_suffix("\n").unwrap_or(&line); + // if the line has newlines in it, consider each line separately - for subline in line.into().split('\n') { + for subline in line.split('\n') { self.lines.push((self.indent, subline.into())); } } diff --git a/libs/wingc/src/lsp/completions.rs b/libs/wingc/src/lsp/completions.rs index fd3cb6cb7d0..0886aae78a1 100644 --- a/libs/wingc/src/lsp/completions.rs +++ b/libs/wingc/src/lsp/completions.rs @@ -5,7 +5,7 @@ use lsp_types::{ use std::cmp::max; use tree_sitter::Point; -use crate::ast::{Expr, ExprKind, Phase, Scope, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType}; +use crate::ast::{Expr, Phase, Scope, Symbol, TypeAnnotation, TypeAnnotationKind, UserDefinedType}; use crate::closure_transform::{CLOSURE_CLASS_PREFIX, PARENT_THIS_NAME}; use crate::diagnostic::WingSpan; use crate::docs::Documented; @@ -13,7 +13,7 @@ use crate::lsp::sync::{FILES, JSII_TYPES}; use crate::type_check::symbol_env::{LookupResult, StatementIdx}; use crate::type_check::{ import_udt_from_jsii, resolve_user_defined_type, ClassLike, Namespace, SymbolKind, Type, Types, UnsafeRef, - CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME, + VariableKind, CLASS_INFLIGHT_INIT_NAME, CLASS_INIT_NAME, }; use crate::visit::{visit_expr, visit_type_annotation, Visit}; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr, WASM_RETURN_ERROR}; @@ -96,11 +96,40 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse } } + let is_new_expression = if let Some(parent) = parent.parent() { + parent.kind() == "new_expression" + } else { + false + }; + + let filter_completions = |completions: Vec| { + if !is_new_expression { + completions + } else { + completions + .iter() + .filter(|c| { + matches!( + c.kind, + Some(CompletionItemKind::CLASS) | Some(CompletionItemKind::MODULE) + ) + }) + .cloned() + .map(|mut c| { + if c.kind == Some(CompletionItemKind::CLASS) { + convert_to_call_completion(&mut c); + } + c + }) + .collect() + } + }; + if let Some(nearest_type_annotation) = scope_visitor.nearest_type_annotation { if let TypeAnnotationKind::UserDefined(udt) = &nearest_type_annotation.kind { let type_lookup = resolve_user_defined_type(udt, found_env, scope_visitor.found_stmt_index.unwrap_or(0)); - let mut completions = if let Ok(type_lookup) = type_lookup { + let completions = if let Ok(type_lookup) = type_lookup { get_completions_from_type(&type_lookup, types, Some(found_env.phase), false) } else { // this is probably a namespace, let's look it up @@ -116,26 +145,7 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse } }; - if parent.parent().expect("custom_type must have a parent node").kind() == "new_expression" { - completions = completions - .iter() - .filter(|c| { - matches!( - c.kind, - Some(CompletionItemKind::CLASS) | Some(CompletionItemKind::MODULE) - ) - }) - .cloned() - .map(|mut c| { - if c.kind == Some(CompletionItemKind::CLASS) { - convert_to_call_completion(&mut c); - } - c - }) - .collect(); - } - - return completions; + return filter_completions(completions); } } @@ -149,11 +159,9 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse .lookup_nested_str(&reference_text, scope_visitor.found_stmt_index) .ok() { - match lookup_thing { - SymbolKind::Type(t) => { - return get_completions_from_type(&t, types, Some(found_env.phase), false); - } - SymbolKind::Variable(v) => return get_completions_from_type(&v.type_, types, Some(found_env.phase), false), + let completions = match lookup_thing { + SymbolKind::Type(t) => get_completions_from_type(&t, types, Some(found_env.phase), false), + SymbolKind::Variable(v) => get_completions_from_type(&v.type_, types, Some(found_env.phase), false), SymbolKind::Namespace(n) => { // If the types in this namespace aren't loaded yet, load them now to get completions if !n.loaded { @@ -170,9 +178,11 @@ pub fn on_completion(params: lsp_types::CompletionParams) -> CompletionResponse import_udt_from_jsii(&mut file_data.types, &mut jsii_types, &udt, &file_data.jsii_imports); }); } - return get_completions_from_namespace(&n, Some(found_env.phase)); + get_completions_from_namespace(&n, Some(found_env.phase)) } - } + }; + + return filter_completions(completions); } return vec![]; @@ -493,7 +503,14 @@ fn get_completions_from_class( .1 .as_variable() .expect("Symbols in classes are always variables"); - if variable.is_static == is_instance { + + let is_static = if let VariableKind::StaticMember = variable.kind { + true + } else { + false + }; + + if is_static == is_instance { return None; } @@ -686,9 +703,9 @@ impl<'a> Visit<'a> for ScopeVisitor<'a> { // We don't want to visit the children of a reference expression // as that will actually be a less useful piece of information // e.g. With `a.b.c.` we are interested in `a.b.c` and not `a.b` - if !matches!(&node.kind, ExprKind::Reference(_)) { - visit_expr(self, node); - } + visit_expr(self, node); + // if !matches!(&node.kind, ExprKind::Reference(_)) { + // } } fn visit_type_annotation(&mut self, node: &'a TypeAnnotation) { @@ -718,6 +735,9 @@ mod tests { ($name:ident, $code:literal, $($assertion:stmt)*) => { #[test] fn $name() { + // NOTE: this is needed for debugging to work regardless of where you run the test + std::env::set_current_dir(env!("CARGO_MANIFEST_DIR")).unwrap(); + let text_document_position = load_file_with_contents($code); let completion = on_completion(CompletionParams { context: None, diff --git a/libs/wingc/src/lsp/hover.rs b/libs/wingc/src/lsp/hover.rs index f2440988dd6..162e4edaac1 100644 --- a/libs/wingc/src/lsp/hover.rs +++ b/libs/wingc/src/lsp/hover.rs @@ -1,5 +1,3 @@ -use lsp_types::{Hover, HoverContents, MarkupContent, MarkupKind, Position}; - use crate::ast::{ Class, Expr, FunctionBody, FunctionDefinition, Reference, Scope, Stmt, StmtKind, Symbol, TypeAnnotation, TypeAnnotationKind, @@ -12,6 +10,7 @@ use crate::type_check::Types; use crate::visit::{self, Visit}; use crate::wasm_util::WASM_RETURN_ERROR; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr}; +use lsp_types::{Hover, HoverContents, MarkupContent, MarkupKind, Position}; pub struct HoverVisitor<'a> { position: Position, @@ -73,6 +72,33 @@ impl<'a> HoverVisitor<'a> { self.current_scope = last_scope; } } + + fn visit_reference_with_member(&mut self, object: &Expr, property: &Symbol) { + if let Some(obj_type) = self.types.get_expr_type(object) { + if property.span.contains(&self.position) { + let new_span = WingSpan { + start: property.span.start, //<<< + end: property.span.end, + file_id: property.span.file_id.clone(), + }; + + if let Some(c) = obj_type.as_class() { + if let Some(v) = c.env.lookup(property, None) { + let docs = v.render_docs(); + self.found = Some((new_span, Some(docs))); + } + } else { + self.found = Some(( + new_span, + self + .types + .get_expr_type(self.current_expr.unwrap()) + .map(|t| t.render_docs()), + )); + } + } + } + } } impl<'a> Visit<'a> for HoverVisitor<'a> { @@ -236,45 +262,13 @@ impl<'a> Visit<'a> for HoverVisitor<'a> { self.found = Some((sym.span.clone(), self.lookup_docs(&sym.name, None))); } } - Reference::InstanceMember { - object, - property, - optional_accessor: _, - } => { - if let Some(obj_type) = self.types.get_expr_type(object) { - if property.span.contains(&self.position) { - let new_span = WingSpan { - start: object.span.start, - end: property.span.end, - file_id: property.span.file_id.clone(), - }; - - if let Some(c) = obj_type.as_class() { - if let Some(v) = c.env.lookup(property, None) { - let docs = v.render_docs(); - self.found = Some((new_span, Some(docs))); - } - } else { - self.found = Some(( - new_span, - self - .types - .get_expr_type(self.current_expr.unwrap()) - .map(|t| t.render_docs()), - )); - } - } - } - } - Reference::TypeMember { type_, property } => { - if property.span.contains(&self.position) { - // lookup type in environment - self.found = Some(( - property.span.clone(), - self.lookup_docs(&type_.full_path_str(), Some(property)), - )); + Reference::TypeReference(t) => { + if t.span.contains(&self.position) { + self.found = Some((t.span.clone(), self.lookup_docs(&t.full_path_str(), None))); } } + Reference::InstanceMember { object, property, .. } => self.visit_reference_with_member(object, property), + Reference::TypeMember { typeobject, property } => self.visit_reference_with_member(&typeobject, property), } visit::visit_reference(self, node); @@ -348,6 +342,9 @@ mod tests { ($name:ident, $code:literal, $($assertion:stmt)*) => { #[test] fn $name() { + // NOTE: this is needed for debugging to work regardless of where you run the test + std::env::set_current_dir(env!("CARGO_MANIFEST_DIR")).unwrap(); + let text_document_position_params = load_file_with_contents($code); let hover = on_hover(HoverParams { text_document_position_params, diff --git a/libs/wingc/src/lsp/signature.rs b/libs/wingc/src/lsp/signature.rs index daef216c379..54c426d51a5 100644 --- a/libs/wingc/src/lsp/signature.rs +++ b/libs/wingc/src/lsp/signature.rs @@ -4,11 +4,11 @@ use lsp_types::{ SignatureInformation, }; -use crate::ast::{Expr, ExprKind, Symbol, TypeAnnotationKind}; +use crate::ast::{Expr, ExprKind, Symbol}; use crate::docs::Documented; use crate::lsp::sync::FILES; -use crate::type_check::{resolve_user_defined_type, CLASS_INIT_NAME}; +use crate::type_check::{resolve_udt_from_expr, resolve_user_defined_type, CLASS_INIT_NAME}; use crate::visit::{visit_expr, Visit}; use crate::wasm_util::{ptr_to_string, string_to_combined_ptr, WASM_RETURN_ERROR}; @@ -43,11 +43,14 @@ pub fn on_signature_help(params: lsp_types::SignatureHelpParams) -> Option { - let t = if let TypeAnnotationKind::UserDefined(udt) = &class.kind { - resolve_user_defined_type(udt, root_scope.env.borrow().as_ref()?, 0).ok()? - } else { + let Some(udt) = resolve_udt_from_expr(class).ok() else { return None; }; + + let Some(t) = resolve_user_defined_type(&udt, root_scope.env.borrow().as_ref()?, 0).ok() else { + return None; + }; + let init_lookup = t.as_class()?.env.lookup( &Symbol { name: CLASS_INIT_NAME.into(), diff --git a/libs/wingc/src/lsp/snapshots/hovers/builtin_instance_method.snap b/libs/wingc/src/lsp/snapshots/hovers/builtin_instance_method.snap index 2d31b78dc7e..ab7bb69bc8f 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/builtin_instance_method.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/builtin_instance_method.snap @@ -7,7 +7,7 @@ contents: range: start: line: 1 - character: 0 + character: 8 end: line: 1 character: 18 diff --git a/libs/wingc/src/lsp/snapshots/hovers/class_property.snap b/libs/wingc/src/lsp/snapshots/hovers/class_property.snap index 1ac0390773e..e5c5ebf01d5 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/class_property.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/class_property.snap @@ -7,7 +7,7 @@ contents: range: start: line: 4 - character: 0 + character: 7 end: line: 4 character: 16 diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 04539a1c7dd..81ad87b5082 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -501,7 +501,7 @@ impl<'s> Parser<'s> { let reference = self.build_reference(&statement_node.child_by_field_name("name").unwrap(), phase)?; if let ExprKind::Reference(r) = reference.kind { Ok(StmtKind::Assignment { - variable: r, + variable: Expr::new(ExprKind::Reference(r), reference.span), value: self.build_expression(&statement_node.child_by_field_name("value").unwrap(), phase)?, }) } else { @@ -699,7 +699,7 @@ impl<'s> Parser<'s> { .err(); } - let return_type = Box::new(TypeAnnotation { + let init_return_type = Box::new(TypeAnnotation { kind: TypeAnnotationKind::UserDefined(UserDefinedType { root: name.clone(), fields: vec![], @@ -715,7 +715,7 @@ impl<'s> Parser<'s> { ), signature: FunctionSignature { parameters, - return_type, + return_type: init_return_type, phase: Phase::Inflight, }, is_static: false, @@ -729,7 +729,7 @@ impl<'s> Parser<'s> { is_static: false, signature: FunctionSignature { parameters, - return_type, + return_type: init_return_type, phase: Phase::Preflight, }, span: self.node_span(&class_element), @@ -804,7 +804,10 @@ impl<'s> Parser<'s> { let parent = if let Some(parent_node) = statement_node.child_by_field_name("parent") { let parent_type = self.build_type_annotation(&parent_node, class_phase)?; match parent_type.kind { - TypeAnnotationKind::UserDefined(parent_type) => Some(parent_type), + TypeAnnotationKind::UserDefined(parent_type) => Some(Expr::new( + ExprKind::Reference(Reference::TypeReference(parent_type)), + self.node_span(&parent_node), + )), _ => { self.with_error::( format!("Parent type must be a user defined type, found {}", parent_type), @@ -1005,6 +1008,51 @@ impl<'s> Parser<'s> { Ok(res) } + fn build_udt(&self, type_node: &Node) -> DiagnosticResult { + match type_node.kind() { + "custom_type" => { + // check if last node is a "." + let last_child = type_node + .child(type_node.child_count() - 1) + .expect("If node is a custom type, it will have at least one child"); + + if last_child.kind() == "." { + // even though we're missing a field, we can still parse the rest of the type + self.add_error("Expected namespaced type", &last_child); + } + + let mut cursor = type_node.walk(); + let udt = UserDefinedType { + root: self.node_symbol(&type_node.child_by_field_name("object").unwrap())?, + fields: type_node + .children_by_field_name("fields", &mut cursor) + .map(|n| self.node_symbol(&n).unwrap()) + .collect(), + span: self.node_span(&type_node), + }; + + Ok(udt) + } + "mutable_container_type" | "immutable_container_type" => { + let container_type = self.node_text(&type_node.child_by_field_name("collection_type").unwrap()); + match container_type { + "ERROR" => self.with_error("Expected builtin container type", type_node)?, + builtin => { + let udt = UserDefinedType { + root: Symbol::global(WINGSDK_STD_MODULE), + fields: vec![Symbol { + name: builtin.to_string(), + span: self.node_span(&type_node), + }], + span: self.node_span(&type_node), + }; + Ok(udt) + } + } + } + other => self.with_error(format!("Expected class. Found {}", other), type_node), + } + } fn build_type_annotation(&self, type_node: &Node, phase: Phase) -> DiagnosticResult { let span = self.node_span(type_node); @@ -1136,14 +1184,14 @@ impl<'s> Parser<'s> { let object_expr = if object_expr.kind() == "json_container_type" { Expr::new( ExprKind::Reference(Reference::TypeMember { - type_: UserDefinedType { - root: Symbol { - name: WINGSDK_STD_MODULE.to_string(), - span: Default::default(), - }, - fields: vec![self.node_symbol(&object_expr)?], - span: self.node_span(&object_expr), - }, + typeobject: Box::new( + UserDefinedType { + root: Symbol::global(WINGSDK_STD_MODULE), + fields: vec![self.node_symbol(&object_expr)?], + span: self.node_span(&object_expr), + } + .to_expression(), + ), property: self.node_symbol(&property)?, }), self.node_span(&object_expr), @@ -1265,7 +1313,11 @@ impl<'s> Parser<'s> { let expression_node = &self.check_error(*exp_node, "expression")?; match expression_node.kind() { "new_expression" => { - let class = self.build_type_annotation(&expression_node.child_by_field_name("class").unwrap(), phase)?; + let class_udt = self.build_udt(&expression_node.child_by_field_name("class").unwrap())?; + let class_udt_exp = Expr::new( + ExprKind::Reference(Reference::TypeReference(class_udt)), + expression_span.clone(), + ); let arg_list = if let Ok(args_node) = self.get_child_field(expression_node, "args") { self.build_arg_list(&args_node, phase) @@ -1283,9 +1335,10 @@ impl<'s> Parser<'s> { } else { None }; + Ok(Expr::new( ExprKind::New { - class, + class: Box::new(class_udt_exp), obj_id, arg_list: arg_list?, obj_scope, @@ -1802,14 +1855,14 @@ impl<'s> Parser<'s> { let type_span = self.node_span(&statement_node.child(0).unwrap()); Ok(StmtKind::Expression(Expr::new( ExprKind::New { - class: TypeAnnotation { - kind: TypeAnnotationKind::UserDefined(UserDefinedType { + class: Box::new(Expr::new( + ExprKind::Reference(Reference::TypeReference(UserDefinedType { root: Symbol::global(WINGSDK_STD_MODULE), fields: vec![Symbol::global(WINGSDK_TEST_CLASS_NAME)], span: type_span.clone(), - }), - span: type_span.clone(), - }, + })), + type_span.clone(), + )), obj_id: Some(test_id), obj_scope: None, arg_list: ArgList { diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index fadd05c2d9e..9f48c604c7f 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -74,6 +74,27 @@ pub enum SymbolKind { Namespace(NamespaceRef), } +#[derive(Debug, Clone)] +pub enum VariableKind { + /// a free variable not associated with a specific type + Free, + + /// an instance member (either of classes or of structs) + InstanceMember, + + /// a class member (or an enum member) + StaticMember, + + /// a type (e.g. `std.Json`) + Type, + + /// a namespace (e.g. `cloud`) + Namespace, + + /// an error placeholder + Error, +} + /// Information about a variable in the environment #[derive(Debug, Clone)] pub struct VariableInfo { @@ -81,14 +102,12 @@ pub struct VariableInfo { pub name: Symbol, /// Type of the variable pub type_: TypeRef, - /// Can the variable be reassigned? + /// Can the variable be reassigned? (only applies to variables and fields) pub reassignable: bool, /// The phase in which this variable exists pub phase: Phase, - /// Is this a member of a class/struct/interface or a free floating variable? - pub is_member: bool, - /// Is this a static or instance variable? (free variables are always static) - pub is_static: bool, + /// The kind of variable + pub kind: VariableKind, } impl SymbolKind { @@ -98,8 +117,11 @@ impl SymbolKind { type_, reassignable, phase, - is_static, - is_member: true, + kind: if is_static { + VariableKind::StaticMember + } else { + VariableKind::InstanceMember + }, }) } @@ -109,8 +131,7 @@ impl SymbolKind { type_, reassignable, phase, - is_static: true, - is_member: false, + kind: VariableKind::Free, }) } @@ -983,6 +1004,10 @@ impl Debug for TypeRef { } } +struct ResolvedExpression { + type_: TypeRef, + phase: Phase, +} pub struct Types { // TODO: Remove the box and change TypeRef and NamespaceRef to just be indices into the types array and namespaces array respectively // Note: we need the box so reallocations of the vec while growing won't change the addresses of the types since they are referenced from the TypeRef struct @@ -1000,7 +1025,7 @@ pub struct Types { nil_idx: usize, err_idx: usize, - type_for_expr: Vec>, + type_for_expr: Vec>, resource_base_type: Option, } @@ -1143,19 +1168,29 @@ impl Types { self.resource_base_type.unwrap() } - /// Stores the type of a given expression node. - pub fn assign_type_to_expr(&mut self, expr: &Expr, type_: TypeRef) { + /// Stores the type and phase of a given expression node. + pub fn assign_type_to_expr(&mut self, expr: &Expr, type_: TypeRef, phase: Phase) { let expr_idx = expr.id; if self.type_for_expr.len() <= expr_idx { self.type_for_expr.resize_with(expr_idx + 1, || None); } - self.type_for_expr[expr_idx] = Some(type_); + self.type_for_expr[expr_idx] = Some(ResolvedExpression { type_, phase }); } /// Obtain the type of a given expression node. Returns None if the expression has not been type checked yet. If /// this is called after type checking, it should always return Some. pub fn get_expr_type(&self, expr: &Expr) -> Option { - self.type_for_expr.get(expr.id).and_then(|t| *t) + self + .type_for_expr + .get(expr.id) + .and_then(|t| t.as_ref().map(|t| t.type_)) + } + + pub fn get_expr_phase(&self, expr: &Expr) -> Option { + self + .type_for_expr + .get(expr.id) + .and_then(|t| t.as_ref().map(|t| t.phase)) } } @@ -1220,6 +1255,15 @@ impl<'a> TypeChecker<'a> { ); } + fn spanned_error_with_var>(&self, spanned: &impl Spanned, message: S) -> (VariableInfo, Phase) { + report_diagnostic(Diagnostic { + message: message.into(), + span: Some(spanned.span()), + }); + + (self.make_error_variable_info(), Phase::Independent) + } + fn spanned_error>(&self, spanned: &impl Spanned, message: S) { report_diagnostic(Diagnostic { message: message.into(), @@ -1244,61 +1288,60 @@ impl<'a> TypeChecker<'a> { self.types.error() } - fn make_error_variable_info(&self, is_static: bool) -> VariableInfo { + fn make_error_variable_info(&self) -> VariableInfo { VariableInfo { name: "".into(), type_: self.types.error(), reassignable: false, phase: Phase::Independent, - is_static, - is_member: false, + kind: VariableKind::Error, } } // Validates types in the expression make sense and returns the expression's inferred type - fn type_check_exp(&mut self, exp: &Expr, env: &SymbolEnv) -> TypeRef { + fn type_check_exp(&mut self, exp: &Expr, env: &SymbolEnv) -> (TypeRef, Phase) { CompilationContext::set(CompilationPhase::TypeChecking, &exp.span); - let t = self.type_check_exp_helper(&exp, env); - self.types.assign_type_to_expr(exp, t); - t + let (t, phase) = self.type_check_exp_helper(&exp, env); + self.types.assign_type_to_expr(exp, t, phase); + (t, phase) } /// Helper function for type_check_exp. This is needed because we want to be able to `return` /// and break early, while still setting the evaluated type on the expression. /// /// Do not use this function directly, use `type_check_exp` instead. - fn type_check_exp_helper(&mut self, exp: &Expr, env: &SymbolEnv) -> TypeRef { + fn type_check_exp_helper(&mut self, exp: &Expr, env: &SymbolEnv) -> (TypeRef, Phase) { match &exp.kind { ExprKind::Literal(lit) => match lit { - Literal::String(_) => self.types.string(), - Literal::Nil => self.types.nil(), + Literal::String(_) => (self.types.string(), Phase::Independent), + Literal::Nil => (self.types.nil(), Phase::Independent), Literal::InterpolatedString(s) => { s.parts.iter().for_each(|part| { if let InterpolatedStringPart::Expr(interpolated_expr) = part { - let exp_type = self.type_check_exp(interpolated_expr, env); + let (exp_type, _) = self.type_check_exp(interpolated_expr, env); self.validate_type_in(exp_type, &self.types.stringables(), interpolated_expr); } }); - self.types.string() + (self.types.string(), Phase::Independent) } - Literal::Number(_) => self.types.number(), - Literal::Boolean(_) => self.types.bool(), + Literal::Number(_) => (self.types.number(), Phase::Independent), + Literal::Boolean(_) => (self.types.bool(), Phase::Independent), }, ExprKind::Binary { op, left, right } => { - let ltype = self.type_check_exp(left, env); - let rtype = self.type_check_exp(right, env); + let (ltype, ltype_phase) = self.type_check_exp(left, env); + let (rtype, _) = self.type_check_exp(right, env); match op { BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { self.validate_type(ltype, self.types.bool(), left); self.validate_type(rtype, self.types.bool(), right); - self.types.bool() + (self.types.bool(), Phase::Independent) } BinaryOperator::AddOrConcat => { if ltype.is_subtype_of(&self.types.number()) && rtype.is_subtype_of(&self.types.number()) { - self.types.number() + (self.types.number(), Phase::Independent) } else if ltype.is_subtype_of(&self.types.string()) && rtype.is_subtype_of(&self.types.string()) { - self.types.string() + (self.types.string(), Phase::Independent) } else { // If any of the types are unresolved (error) then don't report this assuming the error has already been reported if !ltype.is_unresolved() && !rtype.is_unresolved() { @@ -1310,7 +1353,7 @@ impl<'a> TypeChecker<'a> { ), ); } - self.types.error() + self.resolved_error() } } BinaryOperator::Sub @@ -1321,11 +1364,11 @@ impl<'a> TypeChecker<'a> { | BinaryOperator::Power => { self.validate_type(ltype, self.types.number(), left); self.validate_type(rtype, self.types.number(), right); - self.types.number() + (self.types.number(), Phase::Independent) } BinaryOperator::Equal | BinaryOperator::NotEqual => { self.validate_type(rtype, ltype, exp); - self.types.bool() + (self.types.bool(), Phase::Independent) } BinaryOperator::Less | BinaryOperator::LessOrEqual @@ -1333,33 +1376,33 @@ impl<'a> TypeChecker<'a> { | BinaryOperator::GreaterOrEqual => { self.validate_type(ltype, self.types.number(), left); self.validate_type(rtype, self.types.number(), right); - self.types.bool() + (self.types.bool(), Phase::Independent) } BinaryOperator::UnwrapOr => { // Left argument must be an optional type if !ltype.is_option() { self.spanned_error(left, format!("Expected optional type, found \"{}\"", ltype)); - ltype + (ltype, ltype_phase) } else { // Right argument must be a subtype of the inner type of the left argument let inner_type = ltype.maybe_unwrap_option(); self.validate_type(rtype, inner_type, right); - inner_type + (inner_type, ltype_phase) } } } } ExprKind::Unary { op, exp: unary_exp } => { - let type_ = self.type_check_exp(unary_exp, env); + let (type_, phase) = self.type_check_exp(unary_exp, env); match op { - UnaryOperator::Not => self.validate_type(type_, self.types.bool(), unary_exp), - UnaryOperator::Minus => self.validate_type(type_, self.types.number(), unary_exp), + UnaryOperator::Not => (self.validate_type(type_, self.types.bool(), unary_exp), phase), + UnaryOperator::Minus => (self.validate_type(type_, self.types.number(), unary_exp), phase), UnaryOperator::OptionalTest => { if !type_.is_option() { self.spanned_error(unary_exp, format!("Expected optional type, found \"{}\"", type_)); } - self.types.bool() + (self.types.bool(), phase) } } } @@ -1368,26 +1411,41 @@ impl<'a> TypeChecker<'a> { inclusive: _, end, } => { - let stype = self.type_check_exp(start, env); - let etype = self.type_check_exp(end, env); + let (stype, stype_phase) = self.type_check_exp(start, env); + let (etype, _) = self.type_check_exp(end, env); self.validate_type(stype, self.types.number(), start); self.validate_type(etype, self.types.number(), end); - self.types.add_type(Type::Array(stype)) + (self.types.add_type(Type::Array(stype)), stype_phase) + } + ExprKind::Reference(_ref) => { + let (vi, phase) = self.resolve_reference(_ref, env); + (vi.type_, phase) } - ExprKind::Reference(_ref) => self.resolve_reference(_ref, env).type_, ExprKind::New { class, obj_id, arg_list, obj_scope, } => { - // Type check the arguments + // Type check everything + let class_type = self.type_check_exp(&class, env).0; + let obj_scope_type = obj_scope.as_ref().map(|x| self.type_check_exp(x, env).0); + let obj_id_type = obj_id.as_ref().map(|x| self.type_check_exp(x, env).0); let arg_list_types = self.type_check_arg_list(arg_list, env); + let ExprKind::Reference(ref r) = class.kind else { + self.spanned_error(exp,"Must be a reference to a class"); + return (self.types.error(), Phase::Independent); + }; + + let Reference::TypeReference(_) = r else { + self.spanned_error(exp,"Must be a type reference to a class"); + return (self.types.error(), Phase::Independent); + }; + // Lookup the class's type in the env - let type_ = self.resolve_type_annotation(class, env); - let (class_env, class_symbol) = match &*type_ { + let (class_env, class_symbol) = match *class_type { Type::Class(ref class) => { if class.phase == Phase::Independent || env.phase == class.phase { (&class.env, &class.name) @@ -1399,26 +1457,26 @@ impl<'a> TypeChecker<'a> { class.phase, class.name, env.phase ), ); - return self.types.error(); + return (self.types.error(), Phase::Independent); } } // If type is anything we have to assume it's ok to initialize it - Type::Anything => return self.types.anything(), + Type::Anything => return (self.types.anything(), Phase::Independent), // If type is error, we assume the error was already reported and evauate the new expression to error as well - Type::Unresolved => return self.types.error(), + Type::Unresolved => return self.resolved_error(), Type::Struct(_) => { self.spanned_error( class, - format!("Cannot instantiate type \"{}\" because it is a struct and not a class. Use struct instantiation instead.", type_), + format!("Cannot instantiate type \"{}\" because it is a struct and not a class. Use struct instantiation instead.", class_type), ); - return self.types.error(); + return self.resolved_error(); } _ => { self.spanned_error( class, - format!("Cannot instantiate type \"{}\" because it is not a class", type_), + format!("Cannot instantiate type \"{}\" because it is not a class", class_type), ); - return self.types.error(); + return self.resolved_error(); } }; @@ -1440,25 +1498,21 @@ impl<'a> TypeChecker<'a> { span: class_symbol.span.clone(), }, )); - return self.types.error(); + return self.resolved_error(); }; let constructor_sig = constructor_type .as_function_sig() .expect("Expected constructor to be a function signature"); // Verify return type (This should never fail since we define the constructors return type during AST building) - self.validate_type(constructor_sig.return_type, type_, exp); + self.validate_type(constructor_sig.return_type, class_type, exp); self.type_check_arg_list_against_function_sig(&arg_list, &constructor_sig, exp, arg_list_types); - // Type check the scope and id - let obj_scope_type = obj_scope.as_ref().map(|x| self.type_check_exp(x, env)); - let obj_id_type = obj_id.as_ref().map(|x| self.type_check_exp(x, env)); - - let non_std_args = !type_.as_class().unwrap().std_construct_args; + let non_std_args = !class_type.as_class().unwrap().std_construct_args; // If this is a preflight class make sure the object's scope and id are of correct type - if type_.is_preflight_class() { + if class_type.is_preflight_class() { // Get reference to resource object's scope let obj_scope_type = if obj_scope_type.is_none() { // If this returns None, this means we're instantiating a preflight object in the global scope, which is valid @@ -1472,7 +1526,7 @@ impl<'a> TypeChecker<'a> { obj_scope.as_ref().unwrap(), format!( "Cannot set scope of non-standard preflight class \"{}\" using `in`", - type_ + class_type ), ); } @@ -1500,7 +1554,10 @@ impl<'a> TypeChecker<'a> { if non_std_args { self.spanned_error( obj_id.as_ref().unwrap(), - format!("Cannot set id of non-standard preflight class \"{}\" using `as`", type_), + format!( + "Cannot set id of non-standard preflight class \"{}\" using `as`", + class_type + ), ); } } @@ -1513,22 +1570,23 @@ impl<'a> TypeChecker<'a> { self.spanned_error(obj_id, "Inflight classes cannot have an id"); } } - type_ + + (class_type, env.phase) } ExprKind::Call { callee, arg_list } => { // Resolve the function's reference (either a method in the class's env or a function in the current env) - let func_type = self.type_check_exp(callee, env); + let (func_type, callee_phase) = self.type_check_exp(callee, env); let arg_list_types = self.type_check_arg_list(arg_list, env); // If the callee's signature type is unknown, just evaluate the entire call expression as an error if func_type.is_unresolved() { - return self.types.error(); + return self.resolved_error(); } // If the caller's signature is `any`, then just evaluate the entire call expression as `any` if func_type.is_anything() { - return self.types.anything(); + return (self.types.anything(), Phase::Independent); } // Make sure this is a function signature type @@ -1541,20 +1599,20 @@ impl<'a> TypeChecker<'a> { method.type_ } else { self.spanned_error(callee, "Expected a function or method"); - return self.types.error(); + return self.resolved_error(); }; if let Some(sig_type) = handle_type.as_function_sig() { sig_type.clone() } else { self.spanned_error(callee, "Expected a function or method"); - return self.types.error(); + return self.resolved_error(); } } else { self.spanned_error( callee, format!("Expected a function or method, found \"{}\"", func_type), ); - return self.types.error(); + return self.resolved_error(); }; if !env.phase.can_call_to(&func_sig.phase) { @@ -1564,8 +1622,15 @@ impl<'a> TypeChecker<'a> { ); } + // if the function is phase independent, then inherit from the callee + let func_phase = if func_sig.phase == Phase::Independent { + callee_phase + } else { + func_sig.phase + }; + if let Some(value) = self.type_check_arg_list_against_function_sig(arg_list, &func_sig, exp, arg_list_types) { - return value; + return (value, func_phase); } // If the function is "wingc_env", then print out the current environment @@ -1576,14 +1641,14 @@ impl<'a> TypeChecker<'a> { } } - func_sig.return_type + (func_sig.return_type, func_phase) } ExprKind::ArrayLiteral { type_, items } => { // Infer type based on either the explicit type or the value in one of the items let container_type = if let Some(type_) = type_ { self.resolve_type_annotation(type_, env) } else if !items.is_empty() { - let some_val_type = self.type_check_exp(items.iter().next().unwrap(), env); + let (some_val_type, _) = self.type_check_exp(items.iter().next().unwrap(), env); self.types.add_type(Type::Array(some_val_type)) } else { if self.in_json > 0 { @@ -1605,11 +1670,11 @@ impl<'a> TypeChecker<'a> { // Verify all types are the same as the inferred type for v in items.iter() { - let t = self.type_check_exp(v, env); + let (t, _) = self.type_check_exp(v, env); self.check_json_serializable_or_validate_type(t, element_type, v); } - container_type + (container_type, env.phase) } ExprKind::StructLiteral { type_, fields } => { // Find this struct's type in the environment @@ -1619,14 +1684,14 @@ impl<'a> TypeChecker<'a> { let field_types: IndexMap = fields .iter() .map(|(name, exp)| { - let t = self.type_check_exp(exp, env); + let (t, _) = self.type_check_exp(exp, env); (name.clone(), t) }) .collect(); // If we don't have type information for the struct we don't need to validate the fields if struct_type.is_anything() || struct_type.is_unresolved() { - return struct_type; + return (struct_type, env.phase); } // Make sure it really is a struct type @@ -1660,7 +1725,7 @@ impl<'a> TypeChecker<'a> { } } - struct_type + (struct_type, env.phase) } ExprKind::JsonLiteral { is_mut, element } => { if *is_mut { @@ -1677,14 +1742,14 @@ impl<'a> TypeChecker<'a> { } if *is_mut { - self.types.mut_json() + (self.types.mut_json(), env.phase) } else { - self.types.json() + (self.types.json(), env.phase) } } ExprKind::JsonMapLiteral { fields } => { fields.iter().for_each(|(_, v)| { - let t = self.type_check_exp(v, env); + let (t, _) = self.type_check_exp(v, env); // Ensure we dont allow MutJson to Json or vice versa match *t { Type::Json => { @@ -1718,14 +1783,14 @@ impl<'a> TypeChecker<'a> { } }); - self.types.json() + (self.types.json(), env.phase) } ExprKind::MapLiteral { fields, type_ } => { // Infer type based on either the explicit type or the value in one of the fields let container_type = if let Some(type_) = type_ { self.resolve_type_annotation(type_, env) } else if !fields.is_empty() { - let some_val_type = self.type_check_exp(fields.iter().next().unwrap().1, env); + let (some_val_type, _) = self.type_check_exp(fields.iter().next().unwrap().1, env); self.types.add_type(Type::Map(some_val_type)) } else { self.spanned_error(exp, "Cannot infer type of empty map"); @@ -1743,18 +1808,18 @@ impl<'a> TypeChecker<'a> { // Verify all types are the same as the inferred type for (_, v) in fields.iter() { - let t = self.type_check_exp(v, env); + let (t, _) = self.type_check_exp(v, env); self.validate_type(t, value_type, v); } - container_type + (container_type, env.phase) } ExprKind::SetLiteral { type_, items } => { // Infer type based on either the explicit type or the value in one of the items let container_type = if let Some(type_) = type_ { self.resolve_type_annotation(type_, env) } else if !items.is_empty() { - let some_val_type = self.type_check_exp(items.iter().next().unwrap(), env); + let (some_val_type, _) = self.type_check_exp(items.iter().next().unwrap(), env); self.types.add_type(Type::Set(some_val_type)) } else { self.spanned_error(exp, "Cannot infer type of empty set"); @@ -1772,24 +1837,31 @@ impl<'a> TypeChecker<'a> { // Verify all types are the same as the inferred type for v in items.iter() { - let t = self.type_check_exp(v, env); + let (t, _) = self.type_check_exp(v, env); self.validate_type(t, element_type, v); } - container_type + (container_type, env.phase) } ExprKind::FunctionClosure(func_def) => self.type_check_closure(func_def, env), ExprKind::CompilerDebugPanic => { // Handle the debug panic expression (during type-checking) dbg_panic!(); - self.type_error(TypeError { - message: "Panic expression".to_string(), - span: exp.span.clone(), - }) + ( + self.type_error(TypeError { + message: "Panic expression".to_string(), + span: exp.span.clone(), + }), + env.phase, + ) } } } + fn resolved_error(&mut self) -> (UnsafeRef, Phase) { + (self.types.error(), Phase::Independent) + } + fn type_check_arg_list_against_function_sig( &mut self, arg_list: &ArgList, @@ -1865,7 +1937,7 @@ impl<'a> TypeChecker<'a> { None } - fn type_check_closure(&mut self, func_def: &ast::FunctionDefinition, env: &SymbolEnv) -> UnsafeRef { + fn type_check_closure(&mut self, func_def: &ast::FunctionDefinition, env: &SymbolEnv) -> (UnsafeRef, Phase) { // TODO: make sure this function returns on all control paths when there's a return type (can be done by recursively traversing the statements and making sure there's a "return" statements in all control paths) // https://github.com/winglang/wing/issues/457 // Create a type_checker function signature from the AST function definition @@ -1889,9 +1961,9 @@ impl<'a> TypeChecker<'a> { self.inner_scopes.push(scope); - function_type + (function_type, sig.phase) } else { - function_type + (function_type, sig.phase) } } @@ -2129,7 +2201,7 @@ impl<'a> TypeChecker<'a> { let pos_arg_types = arg_list .pos_args .iter() - .map(|pos_arg| self.type_check_exp(pos_arg, env)) + .map(|pos_arg| self.type_check_exp(pos_arg, env).0) .collect(); // Type check the named arguments, e.g. fn(named_arg1: exp4, named_arg2: exp5) @@ -2137,7 +2209,7 @@ impl<'a> TypeChecker<'a> { .named_args .iter() .map(|(sym, expr)| { - let arg_type = self.type_check_exp(&expr, env); + let arg_type = self.type_check_exp(&expr, env).0; (sym.clone(), arg_type) }) .collect::>(); @@ -2164,7 +2236,7 @@ impl<'a> TypeChecker<'a> { type_, } => { let explicit_type = type_.as_ref().map(|t| self.resolve_type_annotation(t, env)); - let inferred_type = self.type_check_exp(initial_value, env); + let (inferred_type, _) = self.type_check_exp(initial_value, env); if inferred_type.is_void() { self.spanned_error( var_name, @@ -2208,7 +2280,7 @@ impl<'a> TypeChecker<'a> { statements, } => { // TODO: Expression must be iterable - let exp_type = self.type_check_exp(iterable, env); + let (exp_type, _) = self.type_check_exp(iterable, env); if !exp_type.is_iterable() { self.spanned_error(iterable, format!("Unable to iterate over \"{}\"", &exp_type)); @@ -2240,7 +2312,7 @@ impl<'a> TypeChecker<'a> { self.inner_scopes.push(statements); } StmtKind::While { condition, statements } => { - let cond_type = self.type_check_exp(condition, env); + let (cond_type, _) = self.type_check_exp(condition, env); self.validate_type(cond_type, self.types.bool(), condition); statements.set_env(SymbolEnv::new( @@ -2261,7 +2333,7 @@ impl<'a> TypeChecker<'a> { var_name, else_statements, } => { - let cond_type = self.type_check_exp(value, env); + let (cond_type, _) = self.type_check_exp(value, env); if !cond_type.is_option() { report_diagnostic(Diagnostic { @@ -2311,7 +2383,7 @@ impl<'a> TypeChecker<'a> { elif_statements, else_statements, } => { - let cond_type = self.type_check_exp(condition, env); + let (cond_type, _) = self.type_check_exp(condition, env); self.validate_type(cond_type, self.types.bool(), condition); statements.set_env(SymbolEnv::new( @@ -2325,7 +2397,7 @@ impl<'a> TypeChecker<'a> { self.inner_scopes.push(statements); for elif_scope in elif_statements { - let cond_type = self.type_check_exp(&elif_scope.condition, env); + let (cond_type, _) = self.type_check_exp(&elif_scope.condition, env); self.validate_type(cond_type, self.types.bool(), condition); (&elif_scope.statements).set_env(SymbolEnv::new( @@ -2355,12 +2427,22 @@ impl<'a> TypeChecker<'a> { self.type_check_exp(e, env); } StmtKind::Assignment { variable, value } => { - let exp_type = self.type_check_exp(value, env); - let var_info = self.resolve_reference(variable, env); - if !var_info.type_.is_unresolved() && !var_info.reassignable { - self.spanned_error(stmt, format!("Variable {} is not reassignable ", variable)); + let (exp_type, _) = self.type_check_exp(value, env); + let (var_type, var_phase) = self.type_check_exp(variable, env); + + // check if the variable can be reassigned + + if let ExprKind::Reference(r) = &variable.kind { + let (var, _) = self.resolve_reference(&r, env); + + if !var_type.is_unresolved() && !var.reassignable { + self.spanned_error(variable, "Variable is not reassignable".to_string()); + } else if var_phase == Phase::Preflight && env.phase == Phase::Inflight { + self.spanned_error(stmt, "Variable cannot be reassigned from inflight".to_string()); + } } - self.validate_type(exp_type, var_info.type_, value); + + self.validate_type(exp_type, var_type, value); } StmtKind::Bring { module_name, @@ -2432,7 +2514,7 @@ impl<'a> TypeChecker<'a> { } StmtKind::Return(exp) => { if let Some(return_expression) = exp { - let return_type = self.type_check_exp(return_expression, env); + let (return_type, _) = self.type_check_exp(return_expression, env); if !env.return_type.is_void() { self.validate_type(return_type, env.return_type, return_expression); } else if env.is_in_function() { @@ -2465,7 +2547,7 @@ impl<'a> TypeChecker<'a> { self.spanned_error(stmt, format!("Cannot declare a {} class in {} scope", phase, env.phase)); } // Verify parent is a known class and get their env - let (parent_class, parent_class_env) = self.extract_parent_class(parent.as_ref(), *phase, name, env, stmt); + let (parent_class, parent_class_env) = self.extract_parent_class(parent.as_ref(), *phase, name, env); // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. let dummy_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); @@ -3249,8 +3331,7 @@ impl<'a> TypeChecker<'a> { type_: v, reassignable, phase: flight, - is_static, - is_member: _, + kind, }) => { // Replace type params in function signatures if let Some(sig) = v.as_function_sig() { @@ -3289,7 +3370,7 @@ impl<'a> TypeChecker<'a> { sym.clone(), self.types.add_type(Type::Function(new_sig)), *reassignable, - *is_static, + matches!(kind, VariableKind::StaticMember), *flight, ), StatementIdx::Top, @@ -3305,7 +3386,13 @@ impl<'a> TypeChecker<'a> { match new_type_class.env.define( // TODO: Original symbol is not available. SymbolKind::Variable should probably expose it &var_name, - SymbolKind::make_member_variable(var_name.clone(), new_var_type, *reassignable, *is_static, *flight), + SymbolKind::make_member_variable( + var_name.clone(), + new_var_type, + *reassignable, + matches!(kind, VariableKind::StaticMember), + *flight, + ), StatementIdx::Top, ) { Err(type_error) => { @@ -3422,12 +3509,14 @@ impl<'a> TypeChecker<'a> { _ => return None, } } - Reference::TypeMember { type_, .. } => { - if path.is_empty() { - return Some(type_.clone()); - } else { - // Type property references cannot be a type name because they have a property - return None; + Reference::TypeReference(type_) => { + return Some(type_.clone()); + } + Reference::TypeMember { typeobject, property } => { + path.push(property.clone()); + current_reference = match &typeobject.kind { + ExprKind::Reference(r) => r, + _ => return None, } } } @@ -3482,19 +3571,19 @@ impl<'a> TypeChecker<'a> { .map(|_| base_udt) } - fn resolve_reference(&mut self, reference: &Reference, env: &SymbolEnv) -> VariableInfo { + fn resolve_reference(&mut self, reference: &Reference, env: &SymbolEnv) -> (VariableInfo, Phase) { match reference { Reference::Identifier(symbol) => { let lookup_res = env.lookup_ext(symbol, Some(self.statement_idx)); if let LookupResult::Found(var, _) = lookup_res { if let Some(var) = var.as_variable() { - var + let phase = var.phase; + (var, phase) } else { - self.spanned_error( + self.spanned_error_with_var( symbol, format!("Expected identifier \"{symbol}\" to be a variable, but it's a {var}",), - ); - self.make_error_variable_info(false) + ) } } else { // Give a specific error message if someone tries to write "print" instead of "log" @@ -3503,7 +3592,7 @@ impl<'a> TypeChecker<'a> { } else { self.type_error(lookup_result_to_type_error(lookup_res, symbol)); } - self.make_error_variable_info(false) + (self.make_error_variable_info(), Phase::Independent) } } Reference::InstanceMember { @@ -3521,7 +3610,7 @@ impl<'a> TypeChecker<'a> { // Create a type reference out of this nested reference and call ourselves again let new_ref = Reference::TypeMember { - type_: user_type_annotation, + typeobject: Box::new(user_type_annotation.to_expression()), property: property.clone(), }; // Replace the reference with the new one, this is unsafe because `reference` isn't mutable and theoretically someone may @@ -3548,13 +3637,21 @@ impl<'a> TypeChecker<'a> { } } - let instance_type = self.type_check_exp(object, env); + let (instance_type, instance_phase) = self.type_check_exp(object, env); + // If resolving the object's type failed, we can't resolve the property either if instance_type.is_unresolved() { - return self.make_error_variable_info(false); + return (self.make_error_variable_info(), Phase::Independent); } - let res = self.resolve_variable_from_instance_type(instance_type, property, env, object); + let property_variable = self.resolve_variable_from_instance_type(instance_type, property, env, object); + + // if the object is `this`, then use the property's phase instead of the object phase + let property_phase = if property_variable.phase == Phase::Independent { + instance_phase + } else { + property_variable.phase + }; // Check if the object is an optional type. If it is ensure the use of optional chaining. let object_type = self.types.get_expr_type(object).unwrap(); @@ -3571,64 +3668,92 @@ impl<'a> TypeChecker<'a> { } if force_reassignable { - VariableInfo { - reassignable: true, - ..res - } + ( + VariableInfo { + reassignable: true, + ..property_variable + }, + property_phase, + ) } else { - res + (property_variable, property_phase) } } - Reference::TypeMember { type_, property } => { - let type_ = self - .resolve_user_defined_type(type_, env, self.statement_idx) - .expect("Type annotation should have been verified by `expr_maybe_type`"); + Reference::TypeReference(udt) => { + let result = self.resolve_user_defined_type(udt, env, self.statement_idx); + let t = match result { + Err(e) => return self.spanned_error_with_var(udt, e.message), + Ok(t) => t, + }; + + let phase = if let Some(c) = t.as_class() { + c.phase + } else { + Phase::Independent + }; + + ( + VariableInfo { + name: Symbol::global(udt.full_path_str()), + type_: t, + reassignable: false, + phase: phase, + kind: VariableKind::Type, + }, + phase, + ) + } + Reference::TypeMember { typeobject, property } => { + let (type_, _) = self.type_check_exp(typeobject, env); + + let ExprKind::Reference(typeref) = &typeobject.kind else { + return self.spanned_error_with_var(typeobject, "Expecting a reference"); + }; + + let Reference::TypeReference(_) = typeref else { + return self.spanned_error_with_var(typeobject, "Expecting a reference to a type"); + }; + match *type_ { Type::Enum(ref e) => { if e.values.contains(property) { - VariableInfo { - name: property.clone(), - type_, - reassignable: false, - phase: Phase::Independent, - is_static: true, - is_member: true, - } + ( + VariableInfo { + name: property.clone(), + kind: VariableKind::StaticMember, + type_, + reassignable: false, + phase: Phase::Independent, + }, + Phase::Independent, + ) } else { - self.spanned_error( + self.spanned_error_with_var( property, format!("Enum \"{}\" does not contain value \"{}\"", type_, property.name), - ); - self.make_error_variable_info(true) + ) } } Type::Class(ref c) => match c.env.lookup(&property, None) { Some(SymbolKind::Variable(v)) => { - if v.is_static { - v.clone() + if let VariableKind::StaticMember = v.kind { + (v.clone(), v.phase) } else { - self.spanned_error( + self.spanned_error_with_var( property, format!( "Class \"{}\" contains a member \"{}\" but it is not static", type_, property.name ), - ); - self.make_error_variable_info(true) + ) } } - _ => { - self.spanned_error( - property, - format!("No member \"{}\" in class \"{}\"", property.name, type_), - ); - self.make_error_variable_info(true) - } + _ => self.spanned_error_with_var( + property, + format!("No member \"{}\" in class \"{}\"", property.name, type_), + ), }, - _ => { - self.spanned_error(property, format!("\"{}\" not a valid reference", reference)); - self.make_error_variable_info(true) - } + _ => self.spanned_error_with_var(property, format!("\"{}\" not a valid reference", reference)), } } } @@ -3650,8 +3775,7 @@ impl<'a> TypeChecker<'a> { type_: instance_type, reassignable: false, phase: env.phase, - is_static: false, - is_member: true, + kind: VariableKind::InstanceMember, }, // Lookup wingsdk std types, hydrating generics if necessary @@ -3725,11 +3849,9 @@ impl<'a> TypeChecker<'a> { ), Type::Struct(ref s) => self.get_property_from_class_like(s, property), _ => { - self.spanned_error( - object, - format!("Property access unsupported on type \"{}\"", instance_type), - ); - self.make_error_variable_info(false) + self + .spanned_error_with_var(property, "Property not found".to_string()) + .0 } } } @@ -3739,18 +3861,19 @@ impl<'a> TypeChecker<'a> { let lookup_res = class.get_env().lookup_ext(property, None); if let LookupResult::Found(field, _) = lookup_res { let var = field.as_variable().expect("Expected property to be a variable"); - if var.is_static { - self.spanned_error( - property, - format!("Cannot access static property \"{property}\" from instance"), - ); - self.make_error_variable_info(false) + if let VariableKind::StaticMember = var.kind { + self + .spanned_error_with_var( + property, + format!("Cannot access static property \"{property}\" from instance"), + ) + .0 } else { var } } else { self.type_error(lookup_result_to_type_error(lookup_res, property)); - self.make_error_variable_info(false) + self.make_error_variable_info() } } @@ -3779,13 +3902,12 @@ impl<'a> TypeChecker<'a> { fn extract_parent_class( &mut self, - parent_udt: Option<&UserDefinedType>, + parent_expr: Option<&Expr>, phase: Phase, name: &Symbol, env: &mut SymbolEnv, - stmt: &Stmt, ) -> (Option, Option) { - if parent_udt.is_none() { + let Some(parent_expr) = parent_expr else { if phase == Phase::Preflight { // if this is a preflight and we don't have a parent, then we implicitly set it to `std.Resource` let t = self.types.resource_base_type(); @@ -3794,24 +3916,23 @@ impl<'a> TypeChecker<'a> { } else { return (None, None); } + }; + + let (parent_type, _) = self.type_check_exp(&parent_expr, env); + + // bail out if we could not resolve the parent type + if parent_type.is_unresolved() { + return (None, None); } + // Safety: we return from the function above so parent_udt cannot be None - let parent_udt = parent_udt.unwrap(); + let parent_udt = resolve_udt_from_expr(parent_expr).unwrap(); if &parent_udt.root == name && parent_udt.fields.is_empty() { self.spanned_error(parent_udt, "Class cannot extend itself".to_string()); return (None, None); } - let parent_type = self.resolve_user_defined_type(parent_udt, env, stmt.idx); - let parent_type = match parent_type { - Ok(t) => t, - Err(e) => { - self.type_error(e); - return (None, None); - } - }; - if let Some(parent_class) = parent_type.as_class() { if parent_class.phase == phase { (Some(parent_type), Some(parent_class.env.get_ref())) @@ -3821,14 +3942,14 @@ impl<'a> TypeChecker<'a> { "{} class {} cannot extend {} class \"{}\"", phase, name, parent_class.phase, parent_class.name ), - span: Some(parent_udt.span.clone()), + span: Some(parent_expr.span.clone()), }); (None, None) } } else { report_diagnostic(Diagnostic { - message: format!("Base class \"{}\" is not a class", parent_type), - span: Some(parent_udt.span.clone()), + message: format!("Expected \"{}\" to be a class", parent_udt), + span: Some(parent_expr.span.clone()), }); (None, None) } @@ -3963,6 +4084,25 @@ where TypeError { message, span } } +/// Resolves a user defined type (e.g. `Foo.Bar.Baz`) to a type reference +pub fn resolve_udt_from_expr(expr: &Expr) -> Result<&UserDefinedType, TypeError> { + let ExprKind::Reference(ref r) = expr.kind else { + return Err(TypeError { + message: "Expected expression to be a reference".to_string(), + span: expr.span.clone(), + }); + }; + + let Reference::TypeReference(udt) = r else { + return Err(TypeError { + message: "Expected reference to be a reference to a type".to_string(), + span: expr.span.clone(), + }); + }; + + Ok(udt) +} + /// Resolves a user defined type (e.g. `Foo.Bar.Baz`) to a type reference pub fn resolve_user_defined_type( user_defined_type: &UserDefinedType, @@ -3974,6 +4114,7 @@ pub fn resolve_user_defined_type( nested_name.extend(user_defined_type.fields.iter().collect_vec()); let lookup_result = env.lookup_nested(&nested_name, Some(statement_idx)); + if let LookupResult::Found(symb_kind, _) = lookup_result { if let SymbolKind::Type(t) = symb_kind { Ok(*t) diff --git a/libs/wingc/src/type_check/class_fields_init.rs b/libs/wingc/src/type_check/class_fields_init.rs index 31f2fba5ded..4d82fd7670d 100644 --- a/libs/wingc/src/type_check/class_fields_init.rs +++ b/libs/wingc/src/type_check/class_fields_init.rs @@ -1,5 +1,5 @@ use crate::{ - ast::{Reference, Stmt, StmtKind, Symbol}, + ast::{ExprKind, Reference, Stmt, StmtKind, Symbol}, visit::{self, Visit}, }; @@ -20,15 +20,18 @@ impl VisitClassInit { impl Visit<'_> for VisitClassInit { fn visit_stmt(&mut self, node: &Stmt) { match &node.kind { - StmtKind::Assignment { variable, value: _ } => match &variable { - Reference::InstanceMember { - property, - object: _, - optional_accessor: _, - } => self.fields.push(Symbol { - name: property.name.clone(), - span: property.span.clone(), - }), + StmtKind::Assignment { variable, value: _ } => match &variable.kind { + ExprKind::Reference(r) => match r { + Reference::InstanceMember { + property, + object: _, + optional_accessor: _, + } => self.fields.push(Symbol { + name: property.name.clone(), + span: property.span.clone(), + }), + _ => (), + }, _ => (), }, _ => (), diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index a33fd9fe08d..53cc4eda6a2 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -312,7 +312,11 @@ impl<'a> JsiiImporter<'a> { self.wing_types.void(), false, false, - Phase::Preflight, + if is_struct { + Phase::Independent + } else { + Phase::Preflight + }, self.jsii_spec.import_statement_idx, ); let new_type_symbol = Self::jsii_name_to_symbol(&type_name, &jsii_interface.location_in_module); @@ -326,7 +330,7 @@ impl<'a> JsiiImporter<'a> { self.wing_types.void(), false, false, - iface_env.phase, + Phase::Independent, // structs are phase-independent self.jsii_spec.import_statement_idx, ), // Dummy env, will be replaced below })), diff --git a/libs/wingc/src/visit.rs b/libs/wingc/src/visit.rs index 713c623382d..c3dcf428b55 100644 --- a/libs/wingc/src/visit.rs +++ b/libs/wingc/src/visit.rs @@ -51,7 +51,7 @@ pub trait Visit<'ast> { fn visit_expr_new( &mut self, node: &'ast Expr, - class: &'ast TypeAnnotation, + class: &'ast Expr, obj_id: &'ast Option>, obj_scope: &'ast Option>, arg_list: &'ast ArgList, @@ -170,7 +170,7 @@ where v.visit_expr(&expr); } StmtKind::Assignment { variable, value } => { - v.visit_reference(variable); + v.visit_expr(variable); v.visit_expr(value); } StmtKind::Return(expr) => { @@ -247,7 +247,7 @@ where } if let Some(extend) = &node.parent { - v.visit_user_defined_type(&extend); + v.visit_expr(&extend); } for implement in &node.implements { @@ -274,14 +274,14 @@ where pub fn visit_expr_new<'ast, V>( v: &mut V, _node: &'ast Expr, - class: &'ast TypeAnnotation, + class: &'ast Expr, obj_id: &'ast Option>, obj_scope: &'ast Option>, arg_list: &'ast ArgList, ) where V: Visit<'ast> + ?Sized, { - v.visit_type_annotation(class); + v.visit_expr(class); v.visit_args(arg_list); if let Some(id) = obj_id { v.visit_expr(&id); @@ -413,8 +413,11 @@ where v.visit_expr(object); v.visit_symbol(property); } - Reference::TypeMember { type_, property } => { + Reference::TypeReference(type_) => { v.visit_user_defined_type(type_); + } + Reference::TypeMember { typeobject, property } => { + v.visit_expr(typeobject); v.visit_symbol(property); } } diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index c89f6ea6ef1..aba7b702922 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -5,7 +5,7 @@ exports[`access_hidden_namespace.w 1`] = ` --> ../../../examples/tests/invalid/access_hidden_namespace.w:7:5 | 7 | new core.NodeJsCode(\\"/tmp/test.txt\\"); // This should fail even though \`fs.TextFile\` extends \`core.FileBase\` because we didn't bring in \`core\` explicitly. - | ^^^^ Unknown symbol \\"core\\" + | ^^^^^^^^^^^^^^^ Unknown symbol \\"core\\" @@ -44,6 +44,20 @@ error: Unknown symbol \\"this\\" | ^^^^ Unknown symbol \\"this\\" +error: Unknown symbol \\"this\\" + --> ../../../examples/tests/invalid/access_static_from_instance.w:7:5 + | +7 | this.instanceField = 1; // Can't access instance fields from static methods + | ^^^^ Unknown symbol \\"this\\" + + +error: Unknown symbol \\"this\\" + --> ../../../examples/tests/invalid/access_static_from_instance.w:8:5 + | +8 | this.f = 1; // Can't access static fields through \`this\` + | ^^^^ Unknown symbol \\"this\\" + + error: Unknown symbol \\"this\\" --> ../../../examples/tests/invalid/access_static_from_instance.w:8:5 | @@ -294,18 +308,18 @@ error: Expected \\"x\\" to be a type but it's a variable | ^ Expected \\"x\\" to be a type but it's a variable -error: Base class \\"S1\\" is not a class +error: Expected \\"S1\\" to be a class --> ../../../examples/tests/invalid/class.w:74:18 | 74 | class C8 extends S1 { - | ^^ Base class \\"S1\\" is not a class + | ^^ Expected \\"S1\\" to be a class -error: Class cannot extend itself +error: Unknown symbol \\"C11\\" --> ../../../examples/tests/invalid/class.w:78:19 | 78 | class C11 extends C11 { - | ^^^ Class cannot extend itself + | ^^^ Unknown symbol \\"C11\\" error: Expected type to be \\"num\\", but got \\"str\\" instead @@ -343,6 +357,13 @@ error: Expected type to be \\"num\\", but got \\"str\\" instead | ^^^^ Expected type to be \\"num\\", but got \\"str\\" instead +error: Variable cannot be reassigned from inflight + --> ../../../examples/tests/invalid/class.w:61:5 + | +61 | this.y = 1; + | ^^^^^^^^^^^ Variable cannot be reassigned from inflight + + Tests 1 failed (1) @@ -528,11 +549,11 @@ exports[`enums.w 1`] = ` | ^^^^ Enum \\"SomeEnum\\" does not contain value \\"FOUR\\" -error: Property access unsupported on type \\"SomeEnum\\" - --> ../../../examples/tests/invalid/enums.w:8:11 +error: Property not found + --> ../../../examples/tests/invalid/enums.w:8:24 | 8 | let two = SomeEnum.TWO.TWO; - | ^^^^^^^^^^^^ Property access unsupported on type \\"SomeEnum\\" + | ^^^ Property not found @@ -751,6 +772,28 @@ exports[`inflight_class_interface_structural_typing.w 1`] = ` +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`inflight_reassign.w 1`] = ` +"error: Variable cannot be reassigned from inflight + --> ../../../examples/tests/invalid/inflight_reassign.w:5:3 + | +5 | xvar = \\"hi\\"; + | ^^^^^^^^^^^^ Variable cannot be reassigned from inflight + + +error: Variable is not reassignable + --> ../../../examples/tests/invalid/inflight_reassign.w:8:3 + | +8 | ylet = 456; + | ^^^^ Variable is not reassignable + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration " @@ -1242,11 +1285,11 @@ error: Expected type to be \\"str\\", but got \\"str?\\" instead | ^^^^^^^^^^^^^^^^^^ Expected type to be \\"str\\", but got \\"str?\\" instead -error: Variable hi is not reassignable +error: Variable is not reassignable --> ../../../examples/tests/invalid/optionals.w:46:3 | 46 | hi = \\"bye\\"; - | ^^^^^^^^^^^ Variable hi is not reassignable + | ^^ Variable is not reassignable @@ -1325,39 +1368,39 @@ Duration " `; exports[`reassign_to_nonreassignable.w 1`] = ` -"error: Variable x is not reassignable +"error: Variable is not reassignable --> ../../../examples/tests/invalid/reassign_to_nonreassignable.w:3:1 | 3 | x = x + 1; - | ^^^^^^^^^^ Variable x is not reassignable + | ^ Variable is not reassignable -error: Variable this.f is not reassignable +error: Variable is not reassignable --> ../../../examples/tests/invalid/reassign_to_nonreassignable.w:28:5 | 28 | this.f = this.f + 1; - | ^^^^^^^^^^^^^^^^^^^^ Variable this.f is not reassignable + | ^^^^^^ Variable is not reassignable -error: Variable this.innerR.inner is not reassignable +error: Variable is not reassignable --> ../../../examples/tests/invalid/reassign_to_nonreassignable.w:30:5 | 30 | this.innerR.inner = 2; - | ^^^^^^^^^^^^^^^^^^^^^^ Variable this.innerR.inner is not reassignable + | ^^^^^^^^^^^^^^^^^ Variable is not reassignable -error: Variable this.inflightF is not reassignable +error: Variable is not reassignable --> ../../../examples/tests/invalid/reassign_to_nonreassignable.w:35:5 | 35 | this.inflightF = this.inflightF + 1; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Variable this.inflightF is not reassignable + | ^^^^^^^^^^^^^^ Variable is not reassignable -error: Variable arg is not reassignable +error: Variable is not reassignable --> ../../../examples/tests/invalid/reassign_to_nonreassignable.w:42:3 | 42 | arg = 0; - | ^^^^^^^^ Variable arg is not reassignable + | ^^^ Variable is not reassignable @@ -1752,10 +1795,10 @@ error: Struct \\"Showtime\\" extends \\"Dazzle\\" which introduces a conflicting error: Cannot instantiate type \\"BucketProps\\" because it is a struct and not a class. Use struct instantiation instead. - --> ../../../examples/tests/invalid/structs.w:47:13 + --> ../../../examples/tests/invalid/structs.w:47:9 | 47 | let x = new cloud.BucketProps(1); - | ^^^^^^^^^^^^^^^^^ Cannot instantiate type \\"BucketProps\\" because it is a struct and not a class. Use struct instantiation instead. + | ^^^^^^^^^^^^^^^^^^^^^^^^ Cannot instantiate type \\"BucketProps\\" because it is a struct and not a class. Use struct instantiation instead. @@ -1889,14 +1932,14 @@ error: Unknown symbol \\"clod\\" --> ../../../examples/tests/invalid/unknown_symbol.w:3:18 | 3 | let bucket = new clod.Bucket(); - | ^^^^ Unknown symbol \\"clod\\" + | ^^^^^^^^^^^ Unknown symbol \\"clod\\" error: Unknown symbol \\"cloudy\\" --> ../../../examples/tests/invalid/unknown_symbol.w:6:17 | 6 | let funky = new cloudy.Funktion(inflight () => { }); - | ^^^^^^ Unknown symbol \\"cloudy\\" + | ^^^^^^^^^^^^^^^ Unknown symbol \\"cloudy\\" error: Unknown symbol \\"y\\" @@ -1920,6 +1963,13 @@ error: Unknown symbol \\"unknown\\" | ^^^^^^^ Unknown symbol \\"unknown\\" +error: Unknown symbol \\"unknown\\" + --> ../../../examples/tests/invalid/unknown_symbol.w:32:1 + | +32 | unknown = 1; + | ^^^^^^^ Unknown symbol \\"unknown\\" + + error: Unknown symbol \\"assert\\" --> ../../../examples/tests/invalid/unknown_symbol.w:20:17 | @@ -1986,11 +2036,11 @@ Duration " `; exports[`void_in_expression_position.w 1`] = ` -"error: Property access unsupported on type \\"void\\" - --> ../../../examples/tests/invalid/void_in_expression_position.w:1:1 +"error: Property not found + --> ../../../examples/tests/invalid/void_in_expression_position.w:1:12 | 1 | log(\\"hey\\").get(\\"x\\"); - | ^^^^^^^^^^ Property access unsupported on type \\"void\\" + | ^^^ Property not found error: Binary operator '+' cannot be applied to operands of type 'num' and 'void'; only (num, num) and (str, str) are supported diff --git a/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_compile_tf-aws.md new file mode 100644 index 00000000000..c22b9b38af1 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_compile_tf-aws.md @@ -0,0 +1,111 @@ +# [custom_obj_id.w](../../../../../examples/tests/valid/custom_obj_id.w) | compile | tf-aws + +## inflight.Foo.js +```js +module.exports = function({ }) { + class Foo { + constructor({ }) { + } + async $inflight_init() { + } + } + return Foo; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.15.2" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[]" + } + }, + "provider": { + "aws": [ + {} + ] + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const $AppBase = $stdlib.core.App.for(process.env.WING_TARGET); +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class Foo extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + } + static _toInflightType(context) { + const self_client_path = "././inflight.Foo.js"; + return $stdlib.core.NodeJsCode.fromInline(` + require("${self_client_path}")({ + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const FooClient = ${Foo._toInflightType(this).text}; + const client = new FooClient({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + _registerBind(host, ops) { + if (ops.includes("$inflight_init")) { + } + super._registerBind(host, ops); + } + } + const foo1 = new Foo(this,"Foo"); + const bar2 = new Foo(this,"bar2"); + {((cond) => {if (!cond) throw new Error("assertion failed: foo1.node.id == \"Foo\"")})((foo1.node.id === "Foo"))}; + {((cond) => {if (!cond) throw new Error("assertion failed: bar2.node.id == \"bar2\"")})((bar2.node.id === "bar2"))}; + } +} +class $App extends $AppBase { + constructor() { + super({ outdir: $outdir, name: "custom_obj_id", plugins: $plugins, isTestEnvironment: $wing_is_test }); + if ($wing_is_test) { + new $Root(this, "env0"); + const $test_runner = this.testRunner; + const $tests = $test_runner.findTests(); + for (let $i = 1; $i < $tests.length; $i++) { + new $Root(this, "env" + $i); + } + } else { + new $Root(this, "Default"); + } + } +} +new $App().synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_test_sim.md new file mode 100644 index 00000000000..3916e3df375 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/valid/custom_obj_id.w_test_sim.md @@ -0,0 +1,12 @@ +# [custom_obj_id.w](../../../../../examples/tests/valid/custom_obj_id.w) | test | sim + +## stdout.log +```log +pass ─ custom_obj_id.wsim (no tests) + + +Tests 1 passed (1) +Test Files 1 passed (1) +Duration +``` +