Giter Club home page Giter Club logo

hylo's People

Contributors

167rgc911 avatar adevress avatar c-bj avatar camio avatar codereport avatar compnerd avatar dabrahams avatar dan-zheng avatar dependabot[bot] avatar esleche avatar finestructure avatar gsfreema avatar jlscheerer avatar joaogui1 avatar koliyo avatar kyouko-taiga avatar lucteo avatar metalymph avatar mptg94 avatar mtk-cognex avatar nickpdemarco avatar peter-evans avatar sean-parent avatar tothambrus11 avatar waltersmuts avatar zachiah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hylo's Issues

Define a format to import checked modules

We need a format to store/load modules that have been type checked, so that we don't have to recompile every sources.

That format should probably include the typed AST of the module's public API and its IR. Then importing a module would consist of injecting the AST in the program under compilation and merge its IR functions.

Implement def-use chains with value semantics

The def-use chain of a value is currently stored in that value. The issue with that approach is that it compels values to have reference semantics, so that their def-use chain can be updated whenever a new instruction is created.

If def-use chains were all stored in an external data structure owned by the module, we could give them pure value semantics. Further, the builder would no longer to allocate them in the shared buffer to guarantee uniqueness.

Overlapping mutable access to expressions during type checking

The interface of the function to type check expression currently expects expr to be inout, so that it may be substituted during type checking (e.g., to replace unresolved declaration references).

However, this choice can lead to an overlapping mutable access when we type check pattern bindings, if the initializer refers to the declaration itself, e.g., val x = x.
This situation is illegal and should be caught during name resolution.
Nonetheless, we should eliminate the possibility of an overlapping access.

The simplest solution is probably to change TypeChecker.check so that it returns the expression checked, along with the solver's solution.

Unexpected failures in CXX tests (parsing, typechecking) need to dump the actual diagnostics

These failures: https://github.com/val-lang/val/blob/96eb07bf89fbb9e92fe0147728562e787df5be7e/Tests/ValTests/CXXTests.swift#L39
https://github.com/val-lang/val/blob/96eb07bf89fbb9e92fe0147728562e787df5be7e/Tests/ValTests/CXXTests.swift#L46

Need to pass the file and line of the test cases to the file: and line: XCTAssert parameters, and pass the text of the diagnostics to as the message parameter.

Diagnostic logging looks really complicated to me and it appears to be coupled into the CLI, so I don't know how to do this job. But without it, test development is going to be really painful.

Gather requirements on associated types in top-level environments

The generic environment of a declaration is responsible for tracking conformance and equivalence relationships on the generic type parameters associated with the declaration. We should also track relationships on associated types, so that we can handle situations where two views are defining the same abstract type.

view V {
  type A
  fun foo() -> A
}

view U {
  type A
  fun bar() -> A
}

// This function should type-check.
fun f<X where X: V, U>(x: X) {
  var a = x.foo()
  a = x.bar()
}

Gathering all relationships in the declaration's environment will also improve the efficiency of lookup requests on associated type. For instance, we'll be able to check the conformance table using X::A as a key rather than going through all of A's conformance and look for Self::A in every one of them.

valc command line usage missing the <emit> options

The options are:
raw-ast : gets you the AST right after the parser, in JSON
raw-ir : performs type checking and outputs the IR before borrow checking
ir : performs type checking and borrow checking, outputs the IR
llvm-ir : outputs LLVM IR
binary : outputs a library or executable (depends on the input)

Human-readable representation of serialized ASTs

The AST implements Codable to get serialized as JSON:

valc --emit raw-ast -o main.json main.val

The resulting json file is barely usable by a human, though. It's just an array of quite shallow data structures. It would be interesting to transform that representation to something human-readable (in JSON or another format).

error message without line number, etc.

Given the following program:

public fun main() {
  let x = 0
  var y = x
  let _ = x
}

the error message is:

error: use of consumed object

which is missing the source location of the error.

Need better failing test diagnostics

/Users/dave/src/val/Tests/ValTests/TypeCheckerTests.swift:43: error: -[ValTests.TypeCheckerTests testTypeChecker] : XCTAssertTrue failed - CallFunction: type checking succeeded, but expected failure
/Users/dave/src/val/Tests/ValTests/TypeCheckerTests.swift:53: error: -[ValTests.TypeCheckerTests testTypeChecker] : XCTAssertTrue failed - CallFunction: 3 unexpected diagnostic(s)

This doesn't tell me what example it failed on, or where the unexpected diagnostics appeared.

The "inconsistent" syntax between "+=" and "="

This is a not an issue but a comment. As a user I find it confusing to require "&" in +=, but not in =. See the code below (I haven't been able to compile Val to verify the code, but from my understanding it's valid).

public fun main() {
  var length = 1
  length = 2
  &length += 2
}

I understand the difference is because = is not a function. But since both mutates value, I would expect they have the same syntax. Otherwise I'd have to stop to think if it's a = or a function when writing the code above. Also, since the language emphasizes mutable value, it would be great if it has a consistent syntax to help user to identify the places where a value is mutated. Just my thoughts, and feel free to close it if you don't agree.

Consider segregating uses of `@testable import`

Using @testable import makes it extremely easy to overlook things that were supposed to be public but were left internal. In general I try to isolate tests of internal components from the public ones for this reason.

Factorize driver code

Test runners have a lot of code in common to execute the compiler pipeline. That includes the following files:

  • Tests/ValTests/CXXTests.swift
  • Tests/ValTests/EmitterTests.swift
  • Tests/ValTests/ParserTests.swift
  • Tests/ValTests/TypeCheckerTests.swift

This code should probably be factorized into a reusable driver.

Create a Windows developer workflow story

The README.md instructions aren't sufficient to build Val on a Windows environment. It would be useful to support this environment to increase the pool of Val developers and confirm cross-platform support for the compiler and, eventually, standard libraries. This ticket is intended to track work towards this end.

Here are a few suggestions for getting started:

  1. Decide on a Windows package manager for pulling in the Swift and libc++ dependencies. I'd suggest Chocolatey as this seems to have the largest community at the moment.
  2. Create a package for the Windows Swift installer available here. Contribute this package to the community so it is widely available.
  3. Build libc++ on Windows using clang+llvm following instructions available here.
  4. Create a package for libc++ on Windows.
  5. Troubleshoot and fix any issues that arise from running the swift .build/checkouts/LLVMSwift/utils/make-pkgconfig.swift command per the instructions.
  6. Troubleshoot and fix any issues with building the compiler.
  7. Update the README.md to indicate Windows support and provide its workflow.

Define the syntax of abstract types

The preliminary design allows for views to declare abstract types, which serve as placeholders in method and property requirements, and are expected to be "instantiated" in conforming types. They correspond to the concept of associated type in Swift.

Here is a "simple" example (syntax attempt). The view Container defines an abstract type Element that is used as a placeholder to specify the type of the mutable property element. To satisfy its conformance to Container, the product type IntBox declares a type alias that maps the name Element onto the concrete type Int.

view Container {
  type Element
  var element: Element
}

type IntBox: Container {
  type Element = Int
  var element: Int
}

One can specify bounds on the type that instantiate an abstract type. In the following example, the bound constrains conforming types to instantiate the abstract type Element with a type that conforms to Hashable.

view HashableContainer {
  type Element where Element Hashable
  var element: Element
}

Ideally, one should be able to specify any kind of type requirement, just as in the clause of a generic type.

view PairOfContainers {
  type Fst where Fst: Container
  type Snd where Snd: Container, Snd::Element == Fst::Element
}

Lets, sinks, and other comments

Here are some observations/first-impressions I had of Val (just from the docs, hello world won't compile -> undefined name 'SourceRepresentable<Name>(value: print,...).

lets and sinks

  • According to the docs, methods and member subscripts can have three overrides for let, inout, and sink. Since the decision to call the let or sink version depends on what you do after the callsite (whether or not the identifier passed into the method is used again), reasoning about local state becomes bizarrely non-linear and overly complicated. It would be nice to explicitly control overload-resolution at the callsite (e.g. via more modifiers to complement &).
  • Coming from C++, the way sinks can 'steal' lets is hard to grapple with. In C++ terms, you are casting away the const from a const-ref then moving the resource, which would be a big red flag. I want to think of lets as captures (or caps :), not to be confused with closure/lambda captures) that grab onto a value, block access to the value from code that can't see the capture, and carry it around immutably until the capture dissolves in the scope where it was created (releasing the value back to a prior owner, or immediately destroying it). By introducing the possibility for lets to be 'emptied', you make non-local reasoning more difficult. Even though the compiler will prevent you from using a let that has been emptied, you have to memorize the implicit interface of a function in order to reason about it - i.e. the interface imposed by all downstream sinks that may only be discoverable with the compiler's help (e.g. when dealing with lets of non-trivial data structures; I can only tremor at the possibility of vectors full of holes; then again, maybe I am overestimating the power of sinks) (also, what happens when you are passing functors around? scary). While indirect partial-sinking may be an optimization (and hence a reason to have a let instead of a sink), it seems like the human cost is too high. I'd prefer if let -> sink always requires an explicit copy, and the compiler just recommends using a var, inout, or sink as input to a sink whenever a let -> sink copy appears superfluous. I realize this let/sink relationship is deeply embedded in the current design. Maybe the idea is users should just copy their local lets if a callee is sinking somewhere down the call stack, causing a compile error in the local function. However, what about cross-library interactions? It seems like one new sink in a tiny library function could propagate ABI-breaking changes throughout an entire ecosystem.
    • I could be completely misunderstanding this, hopefully can test stuff out if I get valc working.

Other comments (trivial syntax)

  • Less is more: function labels. According to the docs, function labels are used for overload resolution to allow multiple same-name functions with the same parameter lists, and for syntactic sugar: "This feature can be used to create APIs that are clear and economical at the use site, especially for functions that accept multiple parameters:". I really appreciate naming parameters at the callsite, but labels seem like a step backward. Labels make it harder (relative to simple parameter names) to reason about a called function because when looking between the function and the callsite, you need to translate parameter names into callsite labels. Is it really important to add clutter in order to get more function overloads? As it is, function overloads are often overused and overloads with similar parameter lists can be a mess for humans to parse through.
  • English is my brain's language: variable definitions. I really prefer seeing the type first. For example, var Double x = 5.0 translates to "define a variable-qualified Double named x and initialize it with value 5.0", whereas var x: Double = 5.0 translates to "define a variable named x that is a Double and initialize it with value 5.0". I don't want to know the name before I know the type, because the name is just a handle onto the real thing. The big exception is automatically-typed variables, which is probably a core motivation for the trailing type syntax (to omit auto). Personally, auto is a very short word that clearly expresses what's happening.
  • Pythonization: opaqueness is frustrating. Using let to make variables may have a nice flow in isolation, but it feels really far away from 'what is the thing you are making/have, exactly?' Let is not a noun - it is a verb. Hence my earlier suggestion to use capture or cap instead :) (currently used for 'closure captures', which could be renamed to 'closure context variables', 'closure attachments', or something).
  • My eyes are slow: optional omission of return. Fewer characters are required, but the tradeoff is extra effort to read code - to check whether or not a function has a trailing return type (not a simple effort for those disgusting functions with 300-character-long declarations, and also an effort for small functions). You lose important 'at a glance' pattern matching that comes with a mandatory return keyword (similarly with yield in subscripts).
  • Multi-line statements: how does it work? How does the compiler figure out where the end of a statement is, if a statement is spread across multiple lines (which may be ergonomically necessary for beefy statements)? Similarly, how does the code reviewer easily identify where statements end (for example, in multi-statement multi-line arithmetic)?

typo in error message from valc

I tried running valc with no arguments to see what would happen:

Error: Missing expected argument '<inputs> ...'

USAGE: cli [--emit <emit>] [--modules] [--nostdlib] [-o <o>] [--verbose] [<inputs> ...]

Shouldn't the cli instead be valc?

Consider Citron for parsing

I found this to be a pleasure to use and it
reduced the amount of code in my project considerably over the corresponding
hand-coded parser.

Create an abstraction over an AST and its property maps

During type checking, the AST must be piped through methods along with a set of other data structures (mostly property maps) that get updated throughout the process. It would be beneficial to wrap these data into a single abstraction, similar to TypedProgram.

Doing so, we could get rid of ScopeHierarchy so that requests about the lexical structure of the program can be handled by a single abstraction, rather than having either the AST or the scope hierarchy require access to the other.

Fatal Error: not implemented

VAL_HOME=$(pwd) swift test
...
Test Case '-[ValTests.ValTests testEval]' started.
VIL/Typestate.swift:850: Fatal error: Not implemented
Exited with signal code 5

Type checker crashes when checking invalid function calls

Minimal example

fun f() {}
fun g() {
  f(x: 0) 
}

The error is caused by the way constraints for a function call are solved. The above program will create the following constraints:

τf == () -> ()
τf == (x: τ0) -> τ1
τ2 <: τ0
τ2 ⊏ ExpressibleByIntLiteral

Because the number of parameters do not match, the solver won't create any additional constraint on τ0 when it solves τf == (x: τ0) -> τ1. Hence, it will eventually have to solve τ2 <: τ0 by guessing τ2 == τ0, causing τ0 to be bound to Int.

As a result, trying to reify (x: τ0) -> τ1 will violate FunType's invariant, as τ0 won't be bound to a parameter type.

Use the term "name" more consistently

A name (Sources/Compiler/AST/Name.swift) is a value that uniquely identifies a single entity in a given scope. It consists of

  • a stem (or base name),
  • an array of labels,
  • an operator notation (optional), and
  • an implement introducer (optional)

For example, foo(a:b:).sink is a name whose stem is foo, the labels are a, b and the introducer is sink. infix+(_:) is another name whose stem is infix, the labels are two nil values and the notation is infix.

Unfortunately, the term "name" is used rather inconsistently in the code.

In declaration nodes:

AST nodes representing declarations parsed from source files often have an identifier property that describe the stem of some name. This property should be called stem to better describe its role.

In SingleEntityDecl:

Declarations that introduce a single entity conform to SingleEntityDecl, which requires a property name. That property is typed as String, but should be typed by Name instead.

Note: It shouldn't be SourceRepresentable<Name> because the name's value is not necessarily source representable. Only the stem is.

In the type checker:

There is a family of lookup methods in TypeChecker that accept a parameter name which should be called stem instead, to better describe its role (note: the first phase of name lookup ignores labels, notations and introducers).

The method TypeChecker.resolve should accept a name rather than its individual parts.

I think `NodeIndex` should be `NodeID`.

These Index types do not function like indices in a collection; they can't be advanced, etc. They are more like keys in a dictionary, but why not just call them IDs?

Undefined symbols for architecture arm64

./valc fac.val

Undefined symbols for architecture arm64:
  "_P3IntO5infix3u2a21_a", referenced from:
      l_F9factorial21_a in main.o
  "_P3IntO5infix3u2d21_a", referenced from:
      l_F9factorial21_a in main.o
  "_P3IntO5infix3u3c21_a", referenced from:
      l_F9factorial21_a in main.o
ld: symbol(s) not found for architecture arm64

"rewrite" branch doesn't build

$ swift test
Fetching https://github.com/apple/swift-argument-parser.git from cache
Fetching https://github.com/apple/swift-collections from cache
Fetched https://github.com/apple/swift-collections (0.89s)
Fetched https://github.com/apple/swift-argument-parser.git (0.91s)
Computing version for https://github.com/apple/swift-collections
Computed https://github.com/apple/swift-collections at 0.0.7 (0.01s)
Computing version for https://github.com/apple/swift-argument-parser.git
Computed https://github.com/apple/swift-argument-parser.git at 0.5.0 (0.01s)
Creating working copy for https://github.com/apple/swift-collections
Working copy of https://github.com/apple/swift-collections resolved at 0.0.7
Creating working copy for https://github.com/apple/swift-argument-parser.git
Working copy of https://github.com/apple/swift-argument-parser.git resolved at 0.5.0
'val': error: invalid custom path 'Library' for target 'ValLibrary'
$ swift version
swift-driver version: 1.45.2 Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
Target: arm64-apple-macosx12.0

Replace handwritten ASTs

Most programs used to test the type checker use a programmatically built AST (e.g., TypeCheckerTests.testTraitDecl). That makes test cases hard to read, update, and maintain.

It would be preferable if those test cases generated ASTs from actual Val code using the parser.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.