Skip to content

Versioning#263

Open
Sdoba16 wants to merge 4 commits into
BlockstreamResearch:masterfrom
Sdoba16:feature/versions
Open

Versioning#263
Sdoba16 wants to merge 4 commits into
BlockstreamResearch:masterfrom
Sdoba16:feature/versions

Conversation

@Sdoba16

@Sdoba16 Sdoba16 commented Mar 31, 2026

Copy link
Copy Markdown
Collaborator

Closes #248

Add compiler versioning for SimplicityHL contracts

This PR introduces strict compiler version requirements

  • Version Directives: Every .simf file must now begin with a simc "..." directive. The directive is safely extracted (ignoring comments) and validated before full AST construction.
  • SemVer Ranges: Added support for standard Semantic Versioning ranges (e.g., >=0.6.0, ^0.6.0, ~0.6.0)
  • Dependency Versioning: The compiler driver now verifies version compatibility across the entire multi-file project (main.simf and all imported --dep libraries)
  • Developer Tooling: Added update_examples.py and a CI workflow to easily manage version across all examples and tests.

Syntax:

simc ">=0.6.0";

fn main() {
    ...
}

Needed simplex addition for this PR:

  • Version Installer: Tooling that will go through the dependencies and look for best fitting version to be installed. The tool will take existing versions from HL releases and install the best-fitted one.

@Sdoba16 Sdoba16 closed this Mar 31, 2026
@Sdoba16 Sdoba16 reopened this Mar 31, 2026
@Sdoba16 Sdoba16 self-assigned this Mar 31, 2026

@stringhandler stringhandler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK.

I've come around to the idea that users should optionally be able to specify the compiler version. However, I don't like the pragma keyword as it deviates from rust, which we haven't done previously. Below are some suggestions.

I also don't think it should be required in the .simc, which will allow us to specify it in a project level manifest for when multiple files are included and for libraries in future. Also, the user should not be forced to provided it even in a single file scenario. This is just extra friction for new developers.

Comment thread examples/array_fold.simf Outdated
@@ -1,3 +1,5 @@
pragma version 0.5.0-rc.0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we maybe rather use a global attribute syntax

#![version("0.5.0-rc.0")]

I would also suggest naming it compiler-version or simc_version or even just simc, so that users don't think it is annotating this file with a version.

Comment thread src/error.rs Outdated
/// Records _what_ happened but not where.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Error {
VersionMismatch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
VersionMismatch {
SimcVersionMismatch {

I prefer stating that it's the compiler version here as well

Comment thread src/ast.rs Outdated

let insertion_point = Span::new(first_item_span.start, first_item_span.start);

return Err(Error::MissingPragma {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think pragmas should be optional

Comment thread src/error.rs Outdated
expected: String,
compiler: String,
},
MissingPragma {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think stating the compiler version should be required

@apoelstra

Copy link
Copy Markdown
Contributor

ab41fec needs rebase

@Sdoba16 Sdoba16 force-pushed the feature/versions branch 3 times, most recently from ef379ee to 975817b Compare April 6, 2026 10:59
@Sdoba16 Sdoba16 force-pushed the feature/versions branch from 975817b to 48917f7 Compare May 4, 2026 13:23
@Sdoba16 Sdoba16 requested review from apoelstra May 4, 2026 13:41
@Sdoba16 Sdoba16 force-pushed the feature/versions branch from e4551f5 to c56e75d Compare May 4, 2026 13:56
@Sdoba16 Sdoba16 marked this pull request as ready for review May 5, 2026 08:17
@Sdoba16 Sdoba16 requested a review from delta1 as a code owner May 5, 2026 08:17
@KyrylR

KyrylR commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Do we have an issue for discussion this?

@Sdoba16

Sdoba16 commented May 8, 2026

Copy link
Copy Markdown
Collaborator Author

Do we have an issue for discussion this?

#248

@LesterEvSe LesterEvSe linked an issue May 15, 2026 that may be closed by this pull request
@KyrylR

KyrylR commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Needs rebase

@apoelstra

Copy link
Copy Markdown
Contributor

c56e75d needs rebase

@Sdoba16 Sdoba16 force-pushed the feature/versions branch from c56e75d to 18ab20c Compare June 3, 2026 16:57
Comment thread doc/versioning.md Outdated
Comment thread doc/versioning.md Outdated
Comment thread src/driver/version_tests.rs Outdated
Comment thread src/driver/version_tests.rs Outdated
@stringhandler

Copy link
Copy Markdown
Contributor

Is it required to add the compiler version? I would say the examples should only specify the minimum version needed to compile, which for all of these would not be necessary as they don't have any breaking changes. The only ones that might need to be specified are the ones using the use and pub keywords.

It is generally fine to include a compiler version, but these look like they need to keep being updated.

Comment thread examples/local_crate/main.simf Outdated
@@ -1,3 +1,5 @@
simc ">=0.6.0";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not just be simc "0.6.0"?

Can we guarantee it will work on 0.7.0?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explained the reason here: #263 (comment)

Comment thread doc/versioning.md Outdated

The SimplicityHL compiler enforces strict version compatibility to prevent contracts from silently breaking due to compiler updates, semantic changes, or new language features.

Every `.simf` file must begin with a compiler version directive:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Every `.simf` file must begin with a compiler version directive:
Every `.simf` file may begin with a compiler version directive:

I think it is incredibly limiting to new developers to enforce this. Surely this should be opt-in.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not fully understand how it will work. If we load a file without a version, then how are we supposed to determine it? Do we assume that it is always correct? What if the functionality in that file becomes deprecated? What if we have some vulnerabilities in the file? Thus, I think we need to make it mandatory for users to use versions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me walk through the two concrete scenarios:

Case 1: File uses newer syntax (e.g., pub or use, introduced in simc 0.6.0)
If no version is specified, the file simply fails to compile on older compilers with a syntax error. The simc field doesn't change this outcome - it just gives a much better error message ("this file requires simc >= 0.6.0") instead of a confusing parser error.

Case 2: File uses deprecated or removed functionality
Same story: without a version constraint, the file fails to compile once that feature is removed. The simc field lets authors signal simc >=0.5.0, <0.6.0 to make that explicit, but it's still an error either way.

On vulnerabilities: the simc version field controls compilation compatibility, not runtime security. Vulnerabilities in Simplicity programs are addressed at the consensus/jet layer, independently of the compiler version.

So omitting the simc field is never silently wrong - the compiler still catches incompatibilities. Making it mandatory adds friction for new developers who just want to write a simple program, and requires them to understand version semantics before writing their first line of code. Keeping it as may means it's there when you need better error messages, but doesn't block the happy path.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, okay

Comment thread src/driver/mod.rs Outdated
for (path, content) in files {
let full_path = format!("workspace/{}", path);
let created_file = canon(&ws.create_file(&full_path, content));
let injected_content = if crate::version::has_version_header(content)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a comment.

Comment thread src/driver/version_tests.rs Outdated

version_test!(
exact_match_all, true, None,
"main.simf" => "simc \"={v}\";\nuse lib::A::foo;\nfn main() {}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"main.simf" => "simc \"={v}\";\nuse lib::A::foo;\nfn main() {}",
"main.simf" => r#"simc ""={v}"";
use lib::A::foo;
fn main() {}"#,

These would be much easier to review if you use raw strings

@apoelstra

Copy link
Copy Markdown
Contributor

cd5b1e9 needs rebase

@Sdoba16 Sdoba16 marked this pull request as draft June 26, 2026 09:07
@Sdoba16 Sdoba16 force-pushed the feature/versions branch 3 times, most recently from 521e33f to 1ac6efd Compare June 29, 2026 10:03
@Sdoba16 Sdoba16 force-pushed the feature/versions branch 2 times, most recently from a413ba1 to fed34ae Compare July 1, 2026 07:17
@Sdoba16 Sdoba16 marked this pull request as ready for review July 1, 2026 07:28
@Sdoba16 Sdoba16 requested a review from LesterEvSe July 1, 2026 07:32
@Sdoba16 Sdoba16 force-pushed the feature/versions branch from fed34ae to 948b18d Compare July 1, 2026 08:47
Comment thread src/version.rs Outdated
Comment thread src/version.rs Outdated
Comment thread src/tracker.rs Outdated
@Sdoba16 Sdoba16 force-pushed the feature/versions branch from 948b18d to 7959e13 Compare July 3, 2026 08:01
Comment thread src/version.rs Outdated
Comment on lines +159 to +193
/// Byte offset of the first content after leading whitespace and comments.
fn skip_trivia(content: &str) -> usize {
let mut rest = content.trim_start();
loop {
if let Some(comment) = rest.strip_prefix("//") {
rest = comment.split_once('\n').map_or("", |(_, tail)| tail);
} else if rest.starts_with("/*") {
rest = Self::skip_block_comment(rest);
} else {
return content.len() - rest.len();
}
rest = rest.trim_start();
}
}

/// The text after a (possibly nested) block comment, matching the lexer;
/// an unterminated comment swallows the rest of the input.
fn skip_block_comment(s: &str) -> &str {
let mut rest = &s["/*".len()..];
let mut depth = 1;
while depth > 0 {
match (rest.find("/*"), rest.find("*/")) {
(Some(open), Some(close)) if open < close => {
depth += 1;
rest = &rest[open + "/*".len()..];
}
(_, Some(close)) => {
depth -= 1;
rest = &rest[close + "*/".len()..];
}
_ => return "",
}
}
rest
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These skip_trivia and skip_block_comment helpers re-implement the comment handling already in the lexer.rs:208-223.

Since both must agree on comment syntax, could we extract that logic into one shared trivia recognizer and call it from both places?

Comment thread src/lib.rs Outdated
Comment on lines +1535 to +1545
/// An omitted directive is allowed: the compiler only enforces a directive that is
/// present, so a directive-less program compiles. The dependency-path equivalent is
/// covered end to end by the CLI test `cli_version_missing_warns_but_compiles`.
#[test]
fn directive_omitted() {
let result = TemplateProgram::new(
"fn main() {}",
Box::new(crate::ast::ElementsJetHinter::new()),
);
assert!(result.is_ok(), "expected success, got: {:?}", result.err());
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's delete it, because we have the same test at version_tests.rs:136

Suggested change
/// An omitted directive is allowed: the compiler only enforces a directive that is
/// present, so a directive-less program compiles. The dependency-path equivalent is
/// covered end to end by the CLI test `cli_version_missing_warns_but_compiles`.
#[test]
fn directive_omitted() {
let result = TemplateProgram::new(
"fn main() {}",
Box::new(crate::ast::ElementsJetHinter::new()),
);
assert!(result.is_ok(), "expected success, got: {:?}", result.err());
}

@Sdoba16 Sdoba16 force-pushed the feature/versions branch from 7959e13 to 946db6c Compare July 3, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Versioning

5 participants